Text Generation
Transformers
Safetensors
dat
babylm
babylm-2026
causal-lm
dual-attention-transformer
nextlat
ema
custom-code
custom_code
Instructions to use abe123/babylm-dat-strict-nextlat-final with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use abe123/babylm-dat-strict-nextlat-final with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-generation", model="abe123/babylm-dat-strict-nextlat-final", trust_remote_code=True)# Load model directly from transformers import AutoModelForCausalLM model = AutoModelForCausalLM.from_pretrained("abe123/babylm-dat-strict-nextlat-final", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use abe123/babylm-dat-strict-nextlat-final with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "abe123/babylm-dat-strict-nextlat-final" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "abe123/babylm-dat-strict-nextlat-final", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }'Use Docker
docker model run hf.co/abe123/babylm-dat-strict-nextlat-final
- SGLang
How to use abe123/babylm-dat-strict-nextlat-final 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 "abe123/babylm-dat-strict-nextlat-final" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "abe123/babylm-dat-strict-nextlat-final", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }'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 "abe123/babylm-dat-strict-nextlat-final" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "abe123/babylm-dat-strict-nextlat-final", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }' - Docker Model Runner
How to use abe123/babylm-dat-strict-nextlat-final with Docker Model Runner:
docker model run hf.co/abe123/babylm-dat-strict-nextlat-final
Publish final EMA checkpoint
Browse files- README.md +53 -0
- attention_config.py +5 -0
- config.json +67 -0
- configuration_dat.py +198 -0
- dat_config.py +283 -0
- dat_core.py +328 -0
- dat_lm.py +475 -0
- dat_symbols.py +263 -0
- masks.py +61 -0
- mlm.py +95 -0
- model.safetensors +3 -0
- modeling_dat.py +290 -0
- special_tokens_map.json +1 -0
- tokenizer.json +0 -0
- tokenizer_config.json +10 -0
- transformer_components.py +282 -0
- transformer_core.py +327 -0
README.md
CHANGED
|
@@ -1,3 +1,56 @@
|
|
| 1 |
---
|
| 2 |
license: apache-2.0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
license: apache-2.0
|
| 3 |
+
library_name: transformers
|
| 4 |
+
pipeline_tag: text-generation
|
| 5 |
+
tags:
|
| 6 |
+
- babylm
|
| 7 |
+
- babylm-2026
|
| 8 |
+
- causal-lm
|
| 9 |
+
- dual-attention-transformer
|
| 10 |
+
- nextlat
|
| 11 |
+
- ema
|
| 12 |
+
- custom-code
|
| 13 |
---
|
| 14 |
+
|
| 15 |
+
# DAT Strict NextLat Final
|
| 16 |
+
|
| 17 |
+
DAT Strict NextLat Final is a causal Dual Attention Transformer language model trained from scratch for the BabyLM 2026 Strict track. It combines self-attention, relational attention, position-relative symbols, and a NextLat auxiliary training objective. The repository contains the tokenizer and custom HuggingFace Transformers code required by the Auto classes.
|
| 18 |
+
|
| 19 |
+
## Architecture
|
| 20 |
+
|
| 21 |
+
- 16 transformer layers
|
| 22 |
+
- Hidden dimension 1,024
|
| 23 |
+
- 12 self-attention heads and 4 relational-attention heads
|
| 24 |
+
- Rotary token positional encoding
|
| 25 |
+
- Shared position-relative symbol retrieval without relative-symbol RoPE
|
| 26 |
+
- RCA relational attention
|
| 27 |
+
- SwiGLU feed-forward layers
|
| 28 |
+
- Vocabulary size 16,384
|
| 29 |
+
- Maximum model sequence length 514
|
| 30 |
+
- Untied token embedding and language-model head
|
| 31 |
+
|
| 32 |
+
## Training
|
| 33 |
+
|
| 34 |
+
The model was trained for ten passes through the BabyLM 2026 Strict corpus under the 100 million word data limit. Training used batches of 42 sequences of 512 tokens, seed 1, Muon for hidden matrix parameters, and LambW for the remaining parameters. The Muon learning rate was 0.02, the auxiliary learning rate was 0.001, weight decay was 0.01, and the schedule used one percent warmup followed by cosine decay to ten percent of the initial rate.
|
| 35 |
+
|
| 36 |
+
NextLat used horizon 1 with cross-entropy weight 0.0, KL weight 0.5, and MSE weight 1.0. An exponential moving average with decay 0.999 was maintained during training. The published final model and checkpoint revisions use the EMA weights. No teacher model, synthetic data augmentation, or multimodal input was used.
|
| 37 |
+
|
| 38 |
+
## Checkpoints
|
| 39 |
+
|
| 40 |
+
The final EMA model is stored on `main`. Intermediate EMA checkpoints use the official BabyLM Strict revision names from `chck_1M` through `chck_1000M`. These names represent cumulative words processed across repeated passes through the corpus.
|
| 41 |
+
|
| 42 |
+
## Loading
|
| 43 |
+
|
| 44 |
+
```python
|
| 45 |
+
from transformers import AutoModelForCausalLM, AutoTokenizer
|
| 46 |
+
|
| 47 |
+
repo_id = "abe123/babylm-dat-strict-nextlat-final"
|
| 48 |
+
tokenizer = AutoTokenizer.from_pretrained(repo_id, trust_remote_code=True)
|
| 49 |
+
model = AutoModelForCausalLM.from_pretrained(repo_id, trust_remote_code=True)
|
| 50 |
+
```
|
| 51 |
+
|
| 52 |
+
Use an explicit immutable commit SHA for reproducible evaluation.
|
| 53 |
+
|
| 54 |
+
## Intended use and limitations
|
| 55 |
+
|
| 56 |
+
This checkpoint is intended for BabyLM evaluation and language-model research. It uses custom model code and therefore requires `trust_remote_code=True` when loaded through HuggingFace Auto classes.
|
attention_config.py
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Shared attention configuration constants."""
|
| 2 |
+
|
| 3 |
+
SUPPORTED_SEQUENCE_BOUNDARY_POLICIES = frozenset(
|
| 4 |
+
{"eos_document", "none", "segment_document"}
|
| 5 |
+
)
|
config.json
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"architectures": [
|
| 3 |
+
"DatForCausalLM"
|
| 4 |
+
],
|
| 5 |
+
"auto_map": {
|
| 6 |
+
"AutoConfig": "configuration_dat.DatConfig",
|
| 7 |
+
"AutoModel": "modeling_dat.DatModel",
|
| 8 |
+
"AutoModelForCausalLM": "modeling_dat.DatForCausalLM",
|
| 9 |
+
"AutoModelForMaskedLM": "modeling_dat.DatForMaskedLM",
|
| 10 |
+
"AutoModelForSequenceClassification": "modeling_dat.DatForSequenceClassification"
|
| 11 |
+
},
|
| 12 |
+
"bos_token_id": 1,
|
| 13 |
+
"dff_factor": 4,
|
| 14 |
+
"dropout": 0.1,
|
| 15 |
+
"dtype": "float32",
|
| 16 |
+
"eos_token_id": 2,
|
| 17 |
+
"ffn_activation": "swiglu",
|
| 18 |
+
"ffn_hidden_dim_mode": "dff_factor",
|
| 19 |
+
"hidden_dim": 1024,
|
| 20 |
+
"init_range": 0.15,
|
| 21 |
+
"init_scheme": "normal_0_02_scaled_projection",
|
| 22 |
+
"max_position_embeddings": 514,
|
| 23 |
+
"max_rel_pos": 512,
|
| 24 |
+
"max_seq_len": 514,
|
| 25 |
+
"mlm_head_enabled": false,
|
| 26 |
+
"model_type": "dat",
|
| 27 |
+
"n_heads_ra": 4,
|
| 28 |
+
"n_heads_sa": 12,
|
| 29 |
+
"n_layers": 16,
|
| 30 |
+
"n_symbols": null,
|
| 31 |
+
"norm_first": true,
|
| 32 |
+
"norm_type": "layernorm",
|
| 33 |
+
"pad_token_id": 3,
|
| 34 |
+
"pe_type": "rope",
|
| 35 |
+
"positional_symbols_sinusoidal": false,
|
| 36 |
+
"ra_n_relations": null,
|
| 37 |
+
"ra_rel_activation": "identity",
|
| 38 |
+
"ra_symmetric_rels": false,
|
| 39 |
+
"ra_type": "rca",
|
| 40 |
+
"relative_symbols_rope": false,
|
| 41 |
+
"relsymbolic_dropout": 0.0,
|
| 42 |
+
"relsymbolic_include_self": false,
|
| 43 |
+
"relsymbolic_neighborhood_size": 2,
|
| 44 |
+
"relsymbolic_normalize_rels": true,
|
| 45 |
+
"relsymbolic_rel_n_heads": 4,
|
| 46 |
+
"relsymbolic_rel_scale": null,
|
| 47 |
+
"relsymbolic_symbolic_attn_n_heads": 4,
|
| 48 |
+
"relsymbolic_symbolic_attn_scale": null,
|
| 49 |
+
"relsymbolic_trainable_symbols": true,
|
| 50 |
+
"relsymbolic_use_bias": false,
|
| 51 |
+
"rope_theta": 10000.0,
|
| 52 |
+
"segment_boundary_token_id": null,
|
| 53 |
+
"sequence_boundary_policy": "eos_document",
|
| 54 |
+
"share_attn_params": false,
|
| 55 |
+
"shared_symbol_retriever": true,
|
| 56 |
+
"symbol_dim": null,
|
| 57 |
+
"symbol_retrieval": "relative",
|
| 58 |
+
"symbolic_attn_n_heads": null,
|
| 59 |
+
"symbolic_use_bias": false,
|
| 60 |
+
"tie_lm_head": false,
|
| 61 |
+
"tie_word_embeddings": false,
|
| 62 |
+
"transformers_version": "5.9.0",
|
| 63 |
+
"use_bias_ffn": true,
|
| 64 |
+
"use_bias_out": false,
|
| 65 |
+
"use_bias_qkv": false,
|
| 66 |
+
"vocab_size": 16384
|
| 67 |
+
}
|
configuration_dat.py
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""HuggingFace PretrainedConfig for the dual-attention (DAT) decoder LM.
|
| 2 |
+
|
| 3 |
+
This is the source-of-truth copy. scripts/convert_dat_to_hf.py copies it into a
|
| 4 |
+
generated HF repository (alongside modeling_dat.py and the flattened model
|
| 5 |
+
source) so the model can be loaded with
|
| 6 |
+
``AutoModelForCausalLM.from_pretrained(path, trust_remote_code=True)``.
|
| 7 |
+
|
| 8 |
+
The config simply carries every field of models.attention.dat.config.DatLMConfig
|
| 9 |
+
so that modeling_dat.py can rebuild the exact DatLMConfig at load time.
|
| 10 |
+
"""
|
| 11 |
+
|
| 12 |
+
from transformers import PretrainedConfig
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
# Every field needed to rebuild DatLMConfig. New exported fields are appended
|
| 16 |
+
# after existing constructor parameters to preserve positional compatibility.
|
| 17 |
+
# modeling_dat.py rebuilds DatLMConfig as
|
| 18 |
+
# DatLMConfig(**{f: getattr(config, f) for f in DAT_LM_FIELDS}).
|
| 19 |
+
DAT_LM_FIELDS = (
|
| 20 |
+
"vocab_size",
|
| 21 |
+
"max_seq_len",
|
| 22 |
+
"pe_type",
|
| 23 |
+
"hidden_dim",
|
| 24 |
+
"n_heads_sa",
|
| 25 |
+
"n_heads_ra",
|
| 26 |
+
"n_layers",
|
| 27 |
+
"dropout",
|
| 28 |
+
"dff_factor",
|
| 29 |
+
"ffn_hidden_dim_mode",
|
| 30 |
+
"ffn_activation",
|
| 31 |
+
"rope_theta",
|
| 32 |
+
"max_rel_pos",
|
| 33 |
+
"init_range",
|
| 34 |
+
"init_scheme",
|
| 35 |
+
"norm_type",
|
| 36 |
+
"norm_first",
|
| 37 |
+
"use_bias_qkv",
|
| 38 |
+
"use_bias_out",
|
| 39 |
+
"use_bias_ffn",
|
| 40 |
+
"tie_lm_head",
|
| 41 |
+
"symbol_dim",
|
| 42 |
+
"n_symbols",
|
| 43 |
+
"symbolic_attn_n_heads",
|
| 44 |
+
"symbol_retrieval",
|
| 45 |
+
"symbolic_use_bias",
|
| 46 |
+
"shared_symbol_retriever",
|
| 47 |
+
"share_attn_params",
|
| 48 |
+
"positional_symbols_sinusoidal",
|
| 49 |
+
"relative_symbols_rope",
|
| 50 |
+
"relsymbolic_rel_n_heads",
|
| 51 |
+
"relsymbolic_symbolic_attn_n_heads",
|
| 52 |
+
"relsymbolic_neighborhood_size",
|
| 53 |
+
"relsymbolic_include_self",
|
| 54 |
+
"relsymbolic_normalize_rels",
|
| 55 |
+
"relsymbolic_trainable_symbols",
|
| 56 |
+
"relsymbolic_dropout",
|
| 57 |
+
"relsymbolic_rel_scale",
|
| 58 |
+
"relsymbolic_symbolic_attn_scale",
|
| 59 |
+
"relsymbolic_use_bias",
|
| 60 |
+
"ra_type",
|
| 61 |
+
"ra_n_relations",
|
| 62 |
+
"ra_rel_activation",
|
| 63 |
+
"ra_symmetric_rels",
|
| 64 |
+
"sequence_boundary_policy",
|
| 65 |
+
"segment_boundary_token_id",
|
| 66 |
+
"pad_token_id",
|
| 67 |
+
"bos_token_id",
|
| 68 |
+
"eos_token_id",
|
| 69 |
+
"mlm_head_enabled",
|
| 70 |
+
)
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
class DatConfig(PretrainedConfig):
|
| 74 |
+
model_type = "dat"
|
| 75 |
+
# Expose the model width under HF's conventional name so generic eval
|
| 76 |
+
# harnesses (e.g. babylm-eval's finetuning head, which reads
|
| 77 |
+
# config.hidden_size) can size their classification head. The model's own
|
| 78 |
+
# field is hidden_dim; this maps the alias onto it for both get and set.
|
| 79 |
+
attribute_map = {"hidden_size": "hidden_dim"}
|
| 80 |
+
|
| 81 |
+
def __init__(
|
| 82 |
+
self,
|
| 83 |
+
vocab_size: int = 16384,
|
| 84 |
+
max_seq_len: int = 513,
|
| 85 |
+
pe_type: str = "rope",
|
| 86 |
+
hidden_dim: int = 256,
|
| 87 |
+
n_heads_sa: int = 2,
|
| 88 |
+
n_heads_ra: int = 2,
|
| 89 |
+
n_layers: int = 4,
|
| 90 |
+
dropout: float = 0.0,
|
| 91 |
+
dff_factor: int = 4,
|
| 92 |
+
ffn_hidden_dim_mode: str = "dff_factor",
|
| 93 |
+
ffn_activation: str = "gelu",
|
| 94 |
+
rope_theta: float = 10000.0,
|
| 95 |
+
max_rel_pos: int | None = None,
|
| 96 |
+
init_range: float = 0.15,
|
| 97 |
+
init_scheme: str = "xavier_uniform",
|
| 98 |
+
norm_type: str = "rmsnorm",
|
| 99 |
+
norm_first: bool = True,
|
| 100 |
+
use_bias_qkv: bool = False,
|
| 101 |
+
use_bias_out: bool = True,
|
| 102 |
+
use_bias_ffn: bool = True,
|
| 103 |
+
tie_lm_head: bool = True,
|
| 104 |
+
symbol_dim: int | None = None,
|
| 105 |
+
n_symbols: int | None = None,
|
| 106 |
+
symbolic_attn_n_heads: int | None = None,
|
| 107 |
+
symbol_retrieval: str = "symbolic",
|
| 108 |
+
symbolic_use_bias: bool = False,
|
| 109 |
+
shared_symbol_retriever: bool = True,
|
| 110 |
+
share_attn_params: bool = False,
|
| 111 |
+
positional_symbols_sinusoidal: bool = False,
|
| 112 |
+
relative_symbols_rope: bool = False,
|
| 113 |
+
relsymbolic_rel_n_heads: int = 4,
|
| 114 |
+
relsymbolic_symbolic_attn_n_heads: int = 4,
|
| 115 |
+
relsymbolic_neighborhood_size: int = 2,
|
| 116 |
+
relsymbolic_include_self: bool = False,
|
| 117 |
+
relsymbolic_normalize_rels: bool = True,
|
| 118 |
+
relsymbolic_trainable_symbols: bool = True,
|
| 119 |
+
relsymbolic_dropout: float = 0.0,
|
| 120 |
+
relsymbolic_rel_scale: float | None = None,
|
| 121 |
+
relsymbolic_symbolic_attn_scale: float | None = None,
|
| 122 |
+
relsymbolic_use_bias: bool = False,
|
| 123 |
+
ra_type: str = "ra",
|
| 124 |
+
ra_n_relations: int | None = None,
|
| 125 |
+
ra_rel_activation: str = "identity",
|
| 126 |
+
ra_symmetric_rels: bool = False,
|
| 127 |
+
sequence_boundary_policy: str = "eos_document",
|
| 128 |
+
segment_boundary_token_id: int | None = None,
|
| 129 |
+
pad_token_id: int = 0,
|
| 130 |
+
bos_token_id: int = 1,
|
| 131 |
+
eos_token_id: int = 2,
|
| 132 |
+
mlm_head_enabled: bool = False,
|
| 133 |
+
**kwargs,
|
| 134 |
+
) -> None:
|
| 135 |
+
self.vocab_size = vocab_size
|
| 136 |
+
self.max_seq_len = max_seq_len
|
| 137 |
+
self.pe_type = pe_type
|
| 138 |
+
self.hidden_dim = hidden_dim
|
| 139 |
+
self.n_heads_sa = n_heads_sa
|
| 140 |
+
self.n_heads_ra = n_heads_ra
|
| 141 |
+
self.n_layers = n_layers
|
| 142 |
+
self.dropout = dropout
|
| 143 |
+
self.dff_factor = dff_factor
|
| 144 |
+
self.ffn_hidden_dim_mode = ffn_hidden_dim_mode
|
| 145 |
+
self.ffn_activation = ffn_activation
|
| 146 |
+
self.rope_theta = rope_theta
|
| 147 |
+
self.max_rel_pos = max_rel_pos
|
| 148 |
+
self.init_range = init_range
|
| 149 |
+
self.init_scheme = init_scheme
|
| 150 |
+
self.norm_type = norm_type
|
| 151 |
+
self.norm_first = norm_first
|
| 152 |
+
self.use_bias_qkv = use_bias_qkv
|
| 153 |
+
self.use_bias_out = use_bias_out
|
| 154 |
+
self.use_bias_ffn = use_bias_ffn
|
| 155 |
+
self.tie_lm_head = tie_lm_head
|
| 156 |
+
self.symbol_dim = symbol_dim
|
| 157 |
+
self.n_symbols = n_symbols
|
| 158 |
+
self.symbolic_attn_n_heads = symbolic_attn_n_heads
|
| 159 |
+
self.symbol_retrieval = symbol_retrieval
|
| 160 |
+
self.symbolic_use_bias = symbolic_use_bias
|
| 161 |
+
self.shared_symbol_retriever = shared_symbol_retriever
|
| 162 |
+
self.share_attn_params = share_attn_params
|
| 163 |
+
self.positional_symbols_sinusoidal = positional_symbols_sinusoidal
|
| 164 |
+
self.relative_symbols_rope = relative_symbols_rope
|
| 165 |
+
self.relsymbolic_rel_n_heads = relsymbolic_rel_n_heads
|
| 166 |
+
self.relsymbolic_symbolic_attn_n_heads = relsymbolic_symbolic_attn_n_heads
|
| 167 |
+
self.relsymbolic_neighborhood_size = relsymbolic_neighborhood_size
|
| 168 |
+
self.relsymbolic_include_self = relsymbolic_include_self
|
| 169 |
+
self.relsymbolic_normalize_rels = relsymbolic_normalize_rels
|
| 170 |
+
self.relsymbolic_trainable_symbols = relsymbolic_trainable_symbols
|
| 171 |
+
self.relsymbolic_dropout = relsymbolic_dropout
|
| 172 |
+
self.relsymbolic_rel_scale = relsymbolic_rel_scale
|
| 173 |
+
self.relsymbolic_symbolic_attn_scale = relsymbolic_symbolic_attn_scale
|
| 174 |
+
self.relsymbolic_use_bias = relsymbolic_use_bias
|
| 175 |
+
self.ra_type = ra_type
|
| 176 |
+
self.ra_n_relations = ra_n_relations
|
| 177 |
+
self.ra_rel_activation = ra_rel_activation
|
| 178 |
+
self.ra_symmetric_rels = ra_symmetric_rels
|
| 179 |
+
self.sequence_boundary_policy = sequence_boundary_policy
|
| 180 |
+
self.segment_boundary_token_id = segment_boundary_token_id
|
| 181 |
+
self.mlm_head_enabled = mlm_head_enabled
|
| 182 |
+
# HF base also exposes max_position_embeddings for generation utilities.
|
| 183 |
+
self.max_position_embeddings = max_seq_len
|
| 184 |
+
# tie_lm_head is the source of truth for weight tying; keep HF's
|
| 185 |
+
# tie_word_embeddings in lockstep so from_pretrained never re-ties an
|
| 186 |
+
# untied head (or fails to tie a tied one). Drop any value coming in via
|
| 187 |
+
# kwargs (e.g. a serialized config.json) so the two cannot disagree.
|
| 188 |
+
# NOTE: tie_lm_head controls only lm_head <-> token_embeddings tying.
|
| 189 |
+
# The mlm_head.linear_out <-> lm_head tying is handled separately by
|
| 190 |
+
# the get_expanded_tied_weights_keys override in modeling_dat.py.
|
| 191 |
+
kwargs.pop("tie_word_embeddings", None)
|
| 192 |
+
super().__init__(
|
| 193 |
+
pad_token_id=pad_token_id,
|
| 194 |
+
bos_token_id=bos_token_id,
|
| 195 |
+
eos_token_id=eos_token_id,
|
| 196 |
+
tie_word_embeddings=tie_lm_head,
|
| 197 |
+
**kwargs,
|
| 198 |
+
)
|
dat_config.py
ADDED
|
@@ -0,0 +1,283 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Configuration for the owned dual-attention decoder LM."""
|
| 2 |
+
|
| 3 |
+
from dataclasses import dataclass
|
| 4 |
+
|
| 5 |
+
from .attention_config import SUPPORTED_SEQUENCE_BOUNDARY_POLICIES
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
SUPPORTED_PE_TYPES = {"sinusoidal", "learned", "relative", "rope", "none"}
|
| 9 |
+
SUPPORTED_SYMBOL_RETRIEVAL = {"symbolic", "positional", "relative", "relsymbolic"}
|
| 10 |
+
SUPPORTED_RA_TYPES = {"ra", "rca", "disrca"}
|
| 11 |
+
SUPPORTED_RA_ACTIVATIONS = {"softmax", "identity", "relu", "tanh", "sigmoid", "gelu"}
|
| 12 |
+
SUPPORTED_FFN_ACTIVATIONS = {"gelu", "relu", "swiglu", "identity"}
|
| 13 |
+
SUPPORTED_FFN_HIDDEN_DIM_MODES = {"dff_factor", "swiglu_parameter_matched"}
|
| 14 |
+
SUPPORTED_INIT_SCHEMES = {"xavier_uniform", "normal_0_02_scaled_projection"}
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
@dataclass(frozen=True)
|
| 18 |
+
class DatLMConfig:
|
| 19 |
+
vocab_size: int
|
| 20 |
+
max_seq_len: int
|
| 21 |
+
pe_type: str = "rope"
|
| 22 |
+
hidden_dim: int = 256
|
| 23 |
+
n_heads_sa: int = 2
|
| 24 |
+
n_heads_ra: int = 2
|
| 25 |
+
n_layers: int = 4
|
| 26 |
+
dropout: float = 0.0
|
| 27 |
+
dff_factor: int = 4
|
| 28 |
+
ffn_hidden_dim_mode: str = "dff_factor"
|
| 29 |
+
ffn_activation: str = "gelu"
|
| 30 |
+
rope_theta: float = 10000.0
|
| 31 |
+
max_rel_pos: int | None = None
|
| 32 |
+
sequence_boundary_policy: str = "eos_document"
|
| 33 |
+
segment_boundary_token_id: int | None = None
|
| 34 |
+
init_range: float = 0.15
|
| 35 |
+
init_scheme: str = "xavier_uniform"
|
| 36 |
+
norm_type: str = "rmsnorm"
|
| 37 |
+
norm_first: bool = True
|
| 38 |
+
use_bias_qkv: bool = False
|
| 39 |
+
use_bias_out: bool = True
|
| 40 |
+
use_bias_ffn: bool = True
|
| 41 |
+
tie_lm_head: bool = True
|
| 42 |
+
symbol_dim: int | None = None
|
| 43 |
+
n_symbols: int | None = None
|
| 44 |
+
symbolic_attn_n_heads: int | None = None
|
| 45 |
+
symbol_retrieval: str = "symbolic"
|
| 46 |
+
symbolic_use_bias: bool = False
|
| 47 |
+
shared_symbol_retriever: bool = True
|
| 48 |
+
share_attn_params: bool = False
|
| 49 |
+
positional_symbols_sinusoidal: bool = False
|
| 50 |
+
relative_symbols_rope: bool = False
|
| 51 |
+
relsymbolic_rel_n_heads: int = 4
|
| 52 |
+
relsymbolic_symbolic_attn_n_heads: int = 4
|
| 53 |
+
relsymbolic_neighborhood_size: int = 2
|
| 54 |
+
relsymbolic_include_self: bool = False
|
| 55 |
+
relsymbolic_normalize_rels: bool = True
|
| 56 |
+
relsymbolic_trainable_symbols: bool = True
|
| 57 |
+
relsymbolic_dropout: float = 0.0
|
| 58 |
+
relsymbolic_rel_scale: float | None = None
|
| 59 |
+
relsymbolic_symbolic_attn_scale: float | None = None
|
| 60 |
+
relsymbolic_use_bias: bool = False
|
| 61 |
+
ra_type: str = "ra"
|
| 62 |
+
ra_n_relations: int | None = None
|
| 63 |
+
ra_rel_activation: str = "identity"
|
| 64 |
+
ra_symmetric_rels: bool = False
|
| 65 |
+
pad_token_id: int = 0
|
| 66 |
+
bos_token_id: int = 1
|
| 67 |
+
eos_token_id: int = 2
|
| 68 |
+
mlm_head_enabled: bool = False
|
| 69 |
+
|
| 70 |
+
def __post_init__(self) -> None:
|
| 71 |
+
if self.vocab_size <= 0:
|
| 72 |
+
raise ValueError(f"vocab_size must be positive, got {self.vocab_size}")
|
| 73 |
+
if self.max_seq_len <= 0:
|
| 74 |
+
raise ValueError(f"max_seq_len must be positive, got {self.max_seq_len}")
|
| 75 |
+
if self.pe_type not in SUPPORTED_PE_TYPES:
|
| 76 |
+
raise ValueError(f"Unsupported pe_type: {self.pe_type}")
|
| 77 |
+
if self.hidden_dim <= 0:
|
| 78 |
+
raise ValueError(f"hidden_dim must be positive, got {self.hidden_dim}")
|
| 79 |
+
if self.n_heads_sa <= 0:
|
| 80 |
+
raise ValueError(f"n_heads_sa must be positive for DAT, got {self.n_heads_sa}")
|
| 81 |
+
if self.n_heads_ra <= 0:
|
| 82 |
+
raise ValueError(f"n_heads_ra must be positive for DAT, got {self.n_heads_ra}")
|
| 83 |
+
total_heads = self.total_n_heads
|
| 84 |
+
if self.hidden_dim % total_heads != 0:
|
| 85 |
+
raise ValueError(
|
| 86 |
+
f"hidden_dim ({self.hidden_dim}) must be divisible by total DAT heads "
|
| 87 |
+
f"({total_heads} = {self.n_heads_sa} SA + {self.n_heads_ra} RA)"
|
| 88 |
+
)
|
| 89 |
+
if self.n_layers <= 0:
|
| 90 |
+
raise ValueError(f"n_layers must be positive, got {self.n_layers}")
|
| 91 |
+
if not 0.0 <= self.dropout < 1.0:
|
| 92 |
+
raise ValueError(f"dropout must be in [0.0, 1.0), got {self.dropout}")
|
| 93 |
+
if self.dff_factor <= 0:
|
| 94 |
+
raise ValueError(f"dff_factor must be positive, got {self.dff_factor}")
|
| 95 |
+
if self.ffn_hidden_dim_mode not in SUPPORTED_FFN_HIDDEN_DIM_MODES:
|
| 96 |
+
raise ValueError(f"Unsupported ffn_hidden_dim_mode for DAT: {self.ffn_hidden_dim_mode}")
|
| 97 |
+
if self.ffn_activation not in SUPPORTED_FFN_ACTIVATIONS:
|
| 98 |
+
raise ValueError(f"Unsupported ffn_activation for DAT: {self.ffn_activation}")
|
| 99 |
+
if self.ffn_hidden_dim_mode == "swiglu_parameter_matched" and self.ffn_activation != "swiglu":
|
| 100 |
+
raise ValueError(
|
| 101 |
+
"ffn_hidden_dim_mode='swiglu_parameter_matched' requires "
|
| 102 |
+
f"ffn_activation='swiglu', got {self.ffn_activation}"
|
| 103 |
+
)
|
| 104 |
+
if self.rope_theta <= 0.0:
|
| 105 |
+
raise ValueError(f"rope_theta must be positive, got {self.rope_theta}")
|
| 106 |
+
if self.max_rel_pos is not None and self.max_rel_pos <= 0:
|
| 107 |
+
raise ValueError(f"max_rel_pos must be positive when provided, got {self.max_rel_pos}")
|
| 108 |
+
if self.sequence_boundary_policy not in SUPPORTED_SEQUENCE_BOUNDARY_POLICIES:
|
| 109 |
+
raise ValueError(
|
| 110 |
+
"Unsupported sequence_boundary_policy for DAT: "
|
| 111 |
+
f"{self.sequence_boundary_policy}"
|
| 112 |
+
)
|
| 113 |
+
if self.sequence_boundary_policy == "segment_document":
|
| 114 |
+
if self.segment_boundary_token_id is None:
|
| 115 |
+
raise ValueError(
|
| 116 |
+
"segment_boundary_token_id is required when "
|
| 117 |
+
"sequence_boundary_policy='segment_document'"
|
| 118 |
+
)
|
| 119 |
+
if self.init_range <= 0.0:
|
| 120 |
+
raise ValueError(f"init_range must be positive, got {self.init_range}")
|
| 121 |
+
if self.init_scheme not in SUPPORTED_INIT_SCHEMES:
|
| 122 |
+
raise ValueError(f"Unsupported init_scheme for DAT: {self.init_scheme}")
|
| 123 |
+
if self.norm_type not in {"layernorm", "rmsnorm"}:
|
| 124 |
+
raise ValueError(
|
| 125 |
+
f"norm_type must be 'layernorm' or 'rmsnorm', got {self.norm_type}"
|
| 126 |
+
)
|
| 127 |
+
if self.pe_type == "rope" and self.head_dim % 2 != 0:
|
| 128 |
+
raise ValueError(
|
| 129 |
+
"RoPE requires even DAT head_dim, got "
|
| 130 |
+
f"{self.head_dim} from hidden_dim={self.hidden_dim}, total_heads={total_heads}"
|
| 131 |
+
)
|
| 132 |
+
if self.pe_type == "sinusoidal" and self.hidden_dim % 2 != 0:
|
| 133 |
+
raise ValueError(f"Sinusoidal encoding requires even hidden_dim, got {self.hidden_dim}")
|
| 134 |
+
if self.share_attn_params and self.n_heads_sa != self.n_heads_ra:
|
| 135 |
+
raise ValueError(
|
| 136 |
+
"share_attn_params=True requires n_heads_sa == n_heads_ra, "
|
| 137 |
+
f"got {self.n_heads_sa} and {self.n_heads_ra}"
|
| 138 |
+
)
|
| 139 |
+
if self.symbol_dim is not None and self.symbol_dim <= 0:
|
| 140 |
+
raise ValueError(f"symbol_dim must be positive when provided, got {self.symbol_dim}")
|
| 141 |
+
if self.symbolic_attn_n_heads is not None and self.symbolic_attn_n_heads <= 0:
|
| 142 |
+
raise ValueError(
|
| 143 |
+
"symbolic_attn_n_heads must be positive when provided, "
|
| 144 |
+
f"got {self.symbolic_attn_n_heads}"
|
| 145 |
+
)
|
| 146 |
+
if self.symbol_retrieval not in SUPPORTED_SYMBOL_RETRIEVAL:
|
| 147 |
+
raise ValueError(f"Unsupported symbol_retrieval for DAT: {self.symbol_retrieval}")
|
| 148 |
+
if self.symbol_retrieval == "symbolic":
|
| 149 |
+
symbolic_heads = self.resolved_symbolic_attn_n_heads
|
| 150 |
+
if self.hidden_dim % symbolic_heads != 0:
|
| 151 |
+
raise ValueError(
|
| 152 |
+
f"hidden_dim ({self.hidden_dim}) must be divisible by symbolic_attn_n_heads "
|
| 153 |
+
f"({symbolic_heads}) for symbolic retrieval"
|
| 154 |
+
)
|
| 155 |
+
if self.resolved_symbol_dim % symbolic_heads != 0:
|
| 156 |
+
raise ValueError(
|
| 157 |
+
f"symbol_dim ({self.resolved_symbol_dim}) must be divisible by symbolic_attn_n_heads "
|
| 158 |
+
f"({symbolic_heads}) for symbolic retrieval"
|
| 159 |
+
)
|
| 160 |
+
if (
|
| 161 |
+
self.symbol_retrieval == "positional"
|
| 162 |
+
and self.positional_symbols_sinusoidal
|
| 163 |
+
and self.resolved_symbol_dim % 2 != 0
|
| 164 |
+
):
|
| 165 |
+
raise ValueError(
|
| 166 |
+
"Sinusoidal positional symbols require even symbol_dim, "
|
| 167 |
+
f"got {self.resolved_symbol_dim}"
|
| 168 |
+
)
|
| 169 |
+
if self.relative_symbols_rope:
|
| 170 |
+
if self.symbol_retrieval != "relative":
|
| 171 |
+
raise ValueError(
|
| 172 |
+
"relative_symbols_rope=True requires symbol_retrieval='relative', "
|
| 173 |
+
f"got {self.symbol_retrieval!r}"
|
| 174 |
+
)
|
| 175 |
+
if self.resolved_symbol_dim % 2 != 0:
|
| 176 |
+
raise ValueError(
|
| 177 |
+
"RoPE relative symbols require even symbol_dim, "
|
| 178 |
+
f"got {self.resolved_symbol_dim}"
|
| 179 |
+
)
|
| 180 |
+
if self.positional_symbols_sinusoidal and self.symbol_retrieval != "positional":
|
| 181 |
+
raise ValueError(
|
| 182 |
+
"positional_symbols_sinusoidal=True requires symbol_retrieval='positional', "
|
| 183 |
+
f"got {self.symbol_retrieval!r}"
|
| 184 |
+
)
|
| 185 |
+
if self.resolved_n_symbols <= 0:
|
| 186 |
+
raise ValueError(f"resolved_n_symbols must be positive, got {self.resolved_n_symbols}")
|
| 187 |
+
if self.symbol_retrieval == "relsymbolic":
|
| 188 |
+
if self.relsymbolic_rel_n_heads <= 0:
|
| 189 |
+
raise ValueError(
|
| 190 |
+
f"relsymbolic_rel_n_heads must be positive, got {self.relsymbolic_rel_n_heads}"
|
| 191 |
+
)
|
| 192 |
+
if self.hidden_dim % self.relsymbolic_rel_n_heads != 0:
|
| 193 |
+
raise ValueError(
|
| 194 |
+
f"hidden_dim ({self.hidden_dim}) must be divisible by "
|
| 195 |
+
f"relsymbolic_rel_n_heads ({self.relsymbolic_rel_n_heads})"
|
| 196 |
+
)
|
| 197 |
+
if self.relsymbolic_symbolic_attn_n_heads <= 0:
|
| 198 |
+
raise ValueError(
|
| 199 |
+
"relsymbolic_symbolic_attn_n_heads must be positive, "
|
| 200 |
+
f"got {self.relsymbolic_symbolic_attn_n_heads}"
|
| 201 |
+
)
|
| 202 |
+
if self.hidden_dim % self.relsymbolic_symbolic_attn_n_heads != 0:
|
| 203 |
+
raise ValueError(
|
| 204 |
+
f"hidden_dim ({self.hidden_dim}) must be divisible by "
|
| 205 |
+
f"relsymbolic_symbolic_attn_n_heads ({self.relsymbolic_symbolic_attn_n_heads})"
|
| 206 |
+
)
|
| 207 |
+
if self.resolved_symbol_dim % self.relsymbolic_symbolic_attn_n_heads != 0:
|
| 208 |
+
raise ValueError(
|
| 209 |
+
f"symbol_dim ({self.resolved_symbol_dim}) must be divisible by "
|
| 210 |
+
f"relsymbolic_symbolic_attn_n_heads ({self.relsymbolic_symbolic_attn_n_heads})"
|
| 211 |
+
)
|
| 212 |
+
if self.relsymbolic_neighborhood_size <= 0:
|
| 213 |
+
raise ValueError(
|
| 214 |
+
"relsymbolic_neighborhood_size must be positive, "
|
| 215 |
+
f"got {self.relsymbolic_neighborhood_size}"
|
| 216 |
+
)
|
| 217 |
+
if not 0.0 <= self.relsymbolic_dropout < 1.0:
|
| 218 |
+
raise ValueError(
|
| 219 |
+
f"relsymbolic_dropout must be in [0.0, 1.0), got {self.relsymbolic_dropout}"
|
| 220 |
+
)
|
| 221 |
+
if self.relsymbolic_rel_scale is not None and self.relsymbolic_rel_scale <= 0.0:
|
| 222 |
+
raise ValueError(
|
| 223 |
+
"relsymbolic_rel_scale must be positive when provided, "
|
| 224 |
+
f"got {self.relsymbolic_rel_scale}"
|
| 225 |
+
)
|
| 226 |
+
if (
|
| 227 |
+
self.relsymbolic_symbolic_attn_scale is not None
|
| 228 |
+
and self.relsymbolic_symbolic_attn_scale <= 0.0
|
| 229 |
+
):
|
| 230 |
+
raise ValueError(
|
| 231 |
+
"relsymbolic_symbolic_attn_scale must be positive when provided, "
|
| 232 |
+
f"got {self.relsymbolic_symbolic_attn_scale}"
|
| 233 |
+
)
|
| 234 |
+
if self.ra_type not in SUPPORTED_RA_TYPES:
|
| 235 |
+
raise ValueError(f"Unsupported ra_type for DAT: {self.ra_type}")
|
| 236 |
+
if self.ra_n_relations is not None and self.ra_n_relations <= 0:
|
| 237 |
+
raise ValueError(
|
| 238 |
+
f"ra_n_relations must be positive when provided, got {self.ra_n_relations}"
|
| 239 |
+
)
|
| 240 |
+
if self.ra_type != "ra" and self.ra_n_relations is not None:
|
| 241 |
+
raise ValueError(f"ra_n_relations applies only to ra_type='ra', got {self.ra_type}")
|
| 242 |
+
if self.ra_type != "ra" and self.ra_symmetric_rels:
|
| 243 |
+
raise ValueError(f"ra_symmetric_rels applies only to ra_type='ra', got {self.ra_type}")
|
| 244 |
+
n_relations = self.resolved_ra_n_relations
|
| 245 |
+
if self.ra_type == "ra" and (self.head_dim * self.n_heads_ra) % n_relations != 0:
|
| 246 |
+
raise ValueError(
|
| 247 |
+
f"head_dim * n_heads_ra ({self.head_dim * self.n_heads_ra}) must be "
|
| 248 |
+
f"divisible by ra_n_relations ({n_relations})"
|
| 249 |
+
)
|
| 250 |
+
if self.ra_rel_activation not in SUPPORTED_RA_ACTIVATIONS:
|
| 251 |
+
raise ValueError(
|
| 252 |
+
f"Unsupported ra_rel_activation for DAT: {self.ra_rel_activation}"
|
| 253 |
+
)
|
| 254 |
+
|
| 255 |
+
@property
|
| 256 |
+
def total_n_heads(self) -> int:
|
| 257 |
+
return self.n_heads_sa + self.n_heads_ra
|
| 258 |
+
|
| 259 |
+
@property
|
| 260 |
+
def head_dim(self) -> int:
|
| 261 |
+
return self.hidden_dim // self.total_n_heads
|
| 262 |
+
|
| 263 |
+
@property
|
| 264 |
+
def resolved_symbol_dim(self) -> int:
|
| 265 |
+
return self.hidden_dim if self.symbol_dim is None else self.symbol_dim
|
| 266 |
+
|
| 267 |
+
@property
|
| 268 |
+
def resolved_symbolic_attn_n_heads(self) -> int:
|
| 269 |
+
return self.total_n_heads if self.symbolic_attn_n_heads is None else self.symbolic_attn_n_heads
|
| 270 |
+
|
| 271 |
+
@property
|
| 272 |
+
def resolved_ffn_hidden_dim(self) -> int:
|
| 273 |
+
if self.ffn_hidden_dim_mode == "swiglu_parameter_matched":
|
| 274 |
+
return int(8 / 3 * self.hidden_dim)
|
| 275 |
+
return self.hidden_dim * self.dff_factor
|
| 276 |
+
|
| 277 |
+
@property
|
| 278 |
+
def resolved_n_symbols(self) -> int:
|
| 279 |
+
return self.max_seq_len if self.n_symbols is None else self.n_symbols
|
| 280 |
+
|
| 281 |
+
@property
|
| 282 |
+
def resolved_ra_n_relations(self) -> int:
|
| 283 |
+
return self.n_heads_ra if self.ra_n_relations is None else self.ra_n_relations
|
dat_core.py
ADDED
|
@@ -0,0 +1,328 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Dual-attention primitives for decoder language models."""
|
| 2 |
+
|
| 3 |
+
import math
|
| 4 |
+
from typing import Optional
|
| 5 |
+
|
| 6 |
+
import torch
|
| 7 |
+
import torch.nn as nn
|
| 8 |
+
|
| 9 |
+
from .transformer_components import PositionalInfo
|
| 10 |
+
from .transformer_core import MultiHeadAttentionBase
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def _activate_scores(scores: torch.Tensor, activation: str) -> torch.Tensor:
|
| 14 |
+
if activation == "softmax":
|
| 15 |
+
return torch.softmax(scores, dim=-1)
|
| 16 |
+
if activation == "identity":
|
| 17 |
+
return scores
|
| 18 |
+
if activation == "relu":
|
| 19 |
+
return torch.relu(scores)
|
| 20 |
+
if activation == "tanh":
|
| 21 |
+
return torch.tanh(scores)
|
| 22 |
+
if activation == "sigmoid":
|
| 23 |
+
return torch.sigmoid(scores)
|
| 24 |
+
if activation == "gelu":
|
| 25 |
+
return torch.nn.functional.gelu(scores)
|
| 26 |
+
raise ValueError(f"Unsupported attention activation: {activation}")
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
class RelationalAttentionBase(MultiHeadAttentionBase):
|
| 30 |
+
def __init__(
|
| 31 |
+
self,
|
| 32 |
+
hidden_dim: int,
|
| 33 |
+
symbol_dim: int,
|
| 34 |
+
n_heads: int,
|
| 35 |
+
total_n_heads: int,
|
| 36 |
+
dropout: float = 0.0,
|
| 37 |
+
n_relations: Optional[int] = None,
|
| 38 |
+
rel_activation: str = "identity",
|
| 39 |
+
symmetric_rels: bool = False,
|
| 40 |
+
use_relative_positional_symbols: bool = False,
|
| 41 |
+
use_bias_qkv: bool = False,
|
| 42 |
+
use_bias_out: bool = True,
|
| 43 |
+
):
|
| 44 |
+
head_dim = hidden_dim // total_n_heads
|
| 45 |
+
output_dim = n_heads * head_dim
|
| 46 |
+
super().__init__(
|
| 47 |
+
query_dim=hidden_dim,
|
| 48 |
+
output_dim=output_dim,
|
| 49 |
+
key_dim=hidden_dim,
|
| 50 |
+
value_dim=symbol_dim,
|
| 51 |
+
n_heads=n_heads,
|
| 52 |
+
hidden_dim=hidden_dim,
|
| 53 |
+
dropout=dropout,
|
| 54 |
+
total_n_heads=total_n_heads,
|
| 55 |
+
activation="softmax",
|
| 56 |
+
use_bias_qkv=use_bias_qkv,
|
| 57 |
+
use_bias_out=use_bias_out,
|
| 58 |
+
)
|
| 59 |
+
self.symbol_dim = symbol_dim
|
| 60 |
+
self.rel_activation = rel_activation
|
| 61 |
+
self.symmetric_rels = symmetric_rels
|
| 62 |
+
self.use_relative_positional_symbols = use_relative_positional_symbols
|
| 63 |
+
self.n_relations = n_heads if n_relations is None else n_relations
|
| 64 |
+
total_rel_dim = self.head_dim * n_heads
|
| 65 |
+
if total_rel_dim % self.n_relations != 0:
|
| 66 |
+
raise ValueError(
|
| 67 |
+
f"head_dim * n_heads ({total_rel_dim}) must be divisible by n_relations "
|
| 68 |
+
f"({self.n_relations})"
|
| 69 |
+
)
|
| 70 |
+
self.rel_proj_dim = total_rel_dim // self.n_relations
|
| 71 |
+
self.rel_scale = 1.0 / math.sqrt(self.rel_proj_dim)
|
| 72 |
+
rel_total_dim = self.n_relations * self.rel_proj_dim
|
| 73 |
+
self.wq_rel = nn.Linear(hidden_dim, rel_total_dim, bias=False)
|
| 74 |
+
self.wk_rel = self.wq_rel if symmetric_rels else nn.Linear(
|
| 75 |
+
hidden_dim,
|
| 76 |
+
rel_total_dim,
|
| 77 |
+
bias=False,
|
| 78 |
+
)
|
| 79 |
+
nn.init.xavier_uniform_(self.wq_rel.weight)
|
| 80 |
+
if self.wk_rel is not self.wq_rel:
|
| 81 |
+
nn.init.xavier_uniform_(self.wk_rel.weight)
|
| 82 |
+
|
| 83 |
+
def _compute_base_attention(
|
| 84 |
+
self,
|
| 85 |
+
x: torch.Tensor,
|
| 86 |
+
mask: Optional[torch.Tensor],
|
| 87 |
+
pos_info: Optional[PositionalInfo],
|
| 88 |
+
) -> torch.Tensor:
|
| 89 |
+
batch_size, seq_len, _ = x.shape
|
| 90 |
+
q = self._reshape_for_multihead(self.q_proj(x), batch_size, seq_len)
|
| 91 |
+
k = self._reshape_for_multihead(self.k_proj(x), batch_size, seq_len)
|
| 92 |
+
scores = self._compute_attn_scores(q, k, pos_info)
|
| 93 |
+
return self._apply_activation_and_mask(scores, mask)
|
| 94 |
+
|
| 95 |
+
def compute_relational_scores(
|
| 96 |
+
self,
|
| 97 |
+
x: torch.Tensor,
|
| 98 |
+
mask: Optional[torch.Tensor],
|
| 99 |
+
return_as: str,
|
| 100 |
+
) -> torch.Tensor:
|
| 101 |
+
batch_size, seq_len, _ = x.shape
|
| 102 |
+
q_rel = self.wq_rel(x).view(batch_size, seq_len, self.n_relations, self.rel_proj_dim)
|
| 103 |
+
k_rel = self.wk_rel(x).view(batch_size, seq_len, self.n_relations, self.rel_proj_dim)
|
| 104 |
+
q_rel = q_rel.transpose(1, 2)
|
| 105 |
+
k_rel = k_rel.transpose(1, 2)
|
| 106 |
+
relations = torch.matmul(q_rel, k_rel.transpose(-2, -1)) * self.rel_scale
|
| 107 |
+
processed_mask = self._process_mask(mask)
|
| 108 |
+
if self.rel_activation == "softmax" and processed_mask is not None:
|
| 109 |
+
relations.masked_fill_(~processed_mask, torch.finfo(relations.dtype).min)
|
| 110 |
+
relations = _activate_scores(relations, self.rel_activation)
|
| 111 |
+
if processed_mask is not None:
|
| 112 |
+
relations = relations.masked_fill(~processed_mask, 0.0)
|
| 113 |
+
if return_as == "vectors":
|
| 114 |
+
return relations.permute(0, 2, 3, 1)
|
| 115 |
+
if return_as == "scores":
|
| 116 |
+
return relations
|
| 117 |
+
raise ValueError(f"return_as must be 'vectors' or 'scores', got {return_as}")
|
| 118 |
+
|
| 119 |
+
def combine_attention_and_relations(
|
| 120 |
+
self,
|
| 121 |
+
attn_weights: torch.Tensor,
|
| 122 |
+
relation_scores: torch.Tensor,
|
| 123 |
+
) -> torch.Tensor:
|
| 124 |
+
return attn_weights * relation_scores
|
| 125 |
+
|
| 126 |
+
def _process_symbols_with_attention(
|
| 127 |
+
self,
|
| 128 |
+
symbols: torch.Tensor,
|
| 129 |
+
attn_weights: torch.Tensor,
|
| 130 |
+
) -> torch.Tensor:
|
| 131 |
+
batch_size, _, seq_len_q, seq_len_k = attn_weights.shape
|
| 132 |
+
values = self.v_proj(symbols)
|
| 133 |
+
if self.use_relative_positional_symbols:
|
| 134 |
+
values = values.view(seq_len_q, seq_len_k, self.n_heads, self.head_dim)
|
| 135 |
+
return torch.einsum("bhij,ijhd->bihd", attn_weights, values)
|
| 136 |
+
|
| 137 |
+
values = values.view(batch_size, seq_len_k, self.n_heads, self.head_dim)
|
| 138 |
+
values = values.transpose(1, 2)
|
| 139 |
+
output = torch.matmul(attn_weights, values)
|
| 140 |
+
return output.transpose(1, 2)
|
| 141 |
+
|
| 142 |
+
def _apply_output_projection(self, output: torch.Tensor) -> torch.Tensor:
|
| 143 |
+
batch_size, seq_len = output.shape[:2]
|
| 144 |
+
output = output.contiguous().view(batch_size, seq_len, self.output_dim)
|
| 145 |
+
output = self.o_proj(output)
|
| 146 |
+
output = self.dropout(output)
|
| 147 |
+
return output
|
| 148 |
+
|
| 149 |
+
def _validate_symbols(self, x: torch.Tensor, symbols: torch.Tensor) -> None:
|
| 150 |
+
if self.use_relative_positional_symbols:
|
| 151 |
+
seq_len = x.shape[1]
|
| 152 |
+
expected_shape = (seq_len, seq_len, self.symbol_dim)
|
| 153 |
+
if tuple(symbols.shape) != expected_shape:
|
| 154 |
+
raise ValueError(
|
| 155 |
+
f"Relative symbols must have shape {expected_shape}, got {tuple(symbols.shape)}"
|
| 156 |
+
)
|
| 157 |
+
|
| 158 |
+
|
| 159 |
+
class RelationalAttention(RelationalAttentionBase):
|
| 160 |
+
def __init__(
|
| 161 |
+
self,
|
| 162 |
+
hidden_dim: int,
|
| 163 |
+
symbol_dim: int,
|
| 164 |
+
n_heads: int,
|
| 165 |
+
total_n_heads: int,
|
| 166 |
+
n_relations: int,
|
| 167 |
+
dropout: float = 0.0,
|
| 168 |
+
rel_activation: str = "identity",
|
| 169 |
+
symmetric_rels: bool = False,
|
| 170 |
+
use_relative_positional_symbols: bool = False,
|
| 171 |
+
use_bias_qkv: bool = False,
|
| 172 |
+
use_bias_out: bool = True,
|
| 173 |
+
):
|
| 174 |
+
super().__init__(
|
| 175 |
+
hidden_dim=hidden_dim,
|
| 176 |
+
symbol_dim=symbol_dim,
|
| 177 |
+
n_heads=n_heads,
|
| 178 |
+
total_n_heads=total_n_heads,
|
| 179 |
+
dropout=dropout,
|
| 180 |
+
n_relations=n_relations,
|
| 181 |
+
rel_activation=rel_activation,
|
| 182 |
+
symmetric_rels=symmetric_rels,
|
| 183 |
+
use_relative_positional_symbols=use_relative_positional_symbols,
|
| 184 |
+
use_bias_qkv=use_bias_qkv,
|
| 185 |
+
use_bias_out=use_bias_out,
|
| 186 |
+
)
|
| 187 |
+
self.wr_proj = nn.Parameter(torch.empty(n_heads, self.head_dim, n_relations))
|
| 188 |
+
nn.init.xavier_uniform_(self.wr_proj)
|
| 189 |
+
|
| 190 |
+
def forward(
|
| 191 |
+
self,
|
| 192 |
+
x: torch.Tensor,
|
| 193 |
+
symbols: torch.Tensor,
|
| 194 |
+
mask: Optional[torch.Tensor],
|
| 195 |
+
pos_info: Optional[PositionalInfo],
|
| 196 |
+
) -> tuple[torch.Tensor, dict[str, torch.Tensor]]:
|
| 197 |
+
self._validate_symbols(x, symbols)
|
| 198 |
+
attn_weights = self._compute_base_attention(x, mask, pos_info)
|
| 199 |
+
attn_weights = self.attn_dropout(attn_weights)
|
| 200 |
+
relation_vectors = self.compute_relational_scores(x, mask, return_as="vectors")
|
| 201 |
+
attended_symbols = self._process_symbols_with_attention(symbols, attn_weights)
|
| 202 |
+
projected_relations = torch.einsum(
|
| 203 |
+
"bhij,bijr,hdr->bihd",
|
| 204 |
+
attn_weights,
|
| 205 |
+
relation_vectors,
|
| 206 |
+
self.wr_proj,
|
| 207 |
+
)
|
| 208 |
+
output = self._apply_output_projection(attended_symbols + projected_relations)
|
| 209 |
+
self.last_attn_weights = attn_weights.detach()
|
| 210 |
+
return output, {"attention": attn_weights, "relations": relation_vectors}
|
| 211 |
+
|
| 212 |
+
|
| 213 |
+
class RelationalCrossAttention(MultiHeadAttentionBase):
|
| 214 |
+
def __init__(
|
| 215 |
+
self,
|
| 216 |
+
hidden_dim: int,
|
| 217 |
+
symbol_dim: int,
|
| 218 |
+
n_heads: int,
|
| 219 |
+
total_n_heads: int,
|
| 220 |
+
dropout: float = 0.0,
|
| 221 |
+
activation: str = "identity",
|
| 222 |
+
use_relative_positional_symbols: bool = False,
|
| 223 |
+
use_bias_qkv: bool = False,
|
| 224 |
+
use_bias_out: bool = True,
|
| 225 |
+
):
|
| 226 |
+
head_dim = hidden_dim // total_n_heads
|
| 227 |
+
super().__init__(
|
| 228 |
+
query_dim=hidden_dim,
|
| 229 |
+
output_dim=n_heads * head_dim,
|
| 230 |
+
key_dim=hidden_dim,
|
| 231 |
+
value_dim=symbol_dim,
|
| 232 |
+
n_heads=n_heads,
|
| 233 |
+
hidden_dim=hidden_dim,
|
| 234 |
+
dropout=dropout,
|
| 235 |
+
total_n_heads=total_n_heads,
|
| 236 |
+
activation=activation,
|
| 237 |
+
use_bias_qkv=use_bias_qkv,
|
| 238 |
+
use_bias_out=use_bias_out,
|
| 239 |
+
)
|
| 240 |
+
self.symbol_dim = symbol_dim
|
| 241 |
+
self.use_relative_positional_symbols = use_relative_positional_symbols
|
| 242 |
+
|
| 243 |
+
def forward(
|
| 244 |
+
self,
|
| 245 |
+
x: torch.Tensor,
|
| 246 |
+
symbols: torch.Tensor,
|
| 247 |
+
mask: Optional[torch.Tensor],
|
| 248 |
+
pos_info: Optional[PositionalInfo],
|
| 249 |
+
) -> tuple[torch.Tensor, dict[str, torch.Tensor]]:
|
| 250 |
+
if self.use_relative_positional_symbols:
|
| 251 |
+
batch_size, seq_len, _ = x.shape
|
| 252 |
+
expected_shape = (seq_len, seq_len, self.symbol_dim)
|
| 253 |
+
if tuple(symbols.shape) != expected_shape:
|
| 254 |
+
raise ValueError(
|
| 255 |
+
f"Relative symbols must have shape {expected_shape}, got {tuple(symbols.shape)}"
|
| 256 |
+
)
|
| 257 |
+
|
| 258 |
+
q = self._reshape_for_multihead(self.q_proj(x), batch_size, seq_len)
|
| 259 |
+
k = self._reshape_for_multihead(self.k_proj(x), batch_size, seq_len)
|
| 260 |
+
values = self.v_proj(symbols).view(seq_len, seq_len, self.n_heads, self.head_dim)
|
| 261 |
+
scores = self._compute_attn_scores(q, k, pos_info)
|
| 262 |
+
weights = self._apply_activation_and_mask(scores, mask)
|
| 263 |
+
weights = self.attn_dropout(weights)
|
| 264 |
+
output = torch.einsum("bhij,ijhd->bihd", weights, values)
|
| 265 |
+
output = output.contiguous().view(batch_size, seq_len, self.output_dim)
|
| 266 |
+
output = self.o_proj(output)
|
| 267 |
+
output = self.dropout(output)
|
| 268 |
+
self.last_attn_weights = weights.detach()
|
| 269 |
+
return output, {"attention": weights}
|
| 270 |
+
|
| 271 |
+
output, weights = super().forward(
|
| 272 |
+
query=x,
|
| 273 |
+
key=x,
|
| 274 |
+
value=symbols,
|
| 275 |
+
mask=mask,
|
| 276 |
+
pos_info=pos_info,
|
| 277 |
+
)
|
| 278 |
+
return output, {"attention": weights}
|
| 279 |
+
|
| 280 |
+
|
| 281 |
+
class DisentangledRelationalCrossAttention(RelationalAttentionBase):
|
| 282 |
+
def __init__(
|
| 283 |
+
self,
|
| 284 |
+
hidden_dim: int,
|
| 285 |
+
symbol_dim: int,
|
| 286 |
+
n_heads: int,
|
| 287 |
+
total_n_heads: int,
|
| 288 |
+
dropout: float = 0.0,
|
| 289 |
+
rel_activation: str = "identity",
|
| 290 |
+
use_relative_positional_symbols: bool = False,
|
| 291 |
+
use_bias_qkv: bool = False,
|
| 292 |
+
use_bias_out: bool = True,
|
| 293 |
+
):
|
| 294 |
+
super().__init__(
|
| 295 |
+
hidden_dim=hidden_dim,
|
| 296 |
+
symbol_dim=symbol_dim,
|
| 297 |
+
n_heads=n_heads,
|
| 298 |
+
total_n_heads=total_n_heads,
|
| 299 |
+
dropout=dropout,
|
| 300 |
+
n_relations=None,
|
| 301 |
+
rel_activation=rel_activation,
|
| 302 |
+
symmetric_rels=False,
|
| 303 |
+
use_relative_positional_symbols=use_relative_positional_symbols,
|
| 304 |
+
use_bias_qkv=use_bias_qkv,
|
| 305 |
+
use_bias_out=use_bias_out,
|
| 306 |
+
)
|
| 307 |
+
|
| 308 |
+
def forward(
|
| 309 |
+
self,
|
| 310 |
+
x: torch.Tensor,
|
| 311 |
+
symbols: torch.Tensor,
|
| 312 |
+
mask: Optional[torch.Tensor],
|
| 313 |
+
pos_info: Optional[PositionalInfo],
|
| 314 |
+
) -> tuple[torch.Tensor, dict[str, torch.Tensor]]:
|
| 315 |
+
self._validate_symbols(x, symbols)
|
| 316 |
+
attn_weights = self._compute_base_attention(x, mask, pos_info)
|
| 317 |
+
relation_scores = self.compute_relational_scores(x, mask, return_as="scores")
|
| 318 |
+
combined_weights = self.attn_dropout(
|
| 319 |
+
self.combine_attention_and_relations(attn_weights, relation_scores)
|
| 320 |
+
)
|
| 321 |
+
output = self._process_symbols_with_attention(symbols, combined_weights)
|
| 322 |
+
output = self._apply_output_projection(output)
|
| 323 |
+
self.last_attn_weights = attn_weights.detach()
|
| 324 |
+
return output, {
|
| 325 |
+
"attention": attn_weights,
|
| 326 |
+
"relations": relation_scores,
|
| 327 |
+
"combined": combined_weights,
|
| 328 |
+
}
|
dat_lm.py
ADDED
|
@@ -0,0 +1,475 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Decoder-only LM built from dual-attention blocks."""
|
| 2 |
+
|
| 3 |
+
import math
|
| 4 |
+
from typing import Optional
|
| 5 |
+
|
| 6 |
+
import torch
|
| 7 |
+
import torch.nn as nn
|
| 8 |
+
import torch.nn.functional as F
|
| 9 |
+
from torch.nn.utils.rnn import pad_sequence
|
| 10 |
+
|
| 11 |
+
from .dat_config import DatLMConfig
|
| 12 |
+
from .dat_core import (
|
| 13 |
+
DisentangledRelationalCrossAttention,
|
| 14 |
+
RelationalAttention,
|
| 15 |
+
RelationalCrossAttention,
|
| 16 |
+
)
|
| 17 |
+
from .transformer_core import SelfAttention
|
| 18 |
+
from .dat_symbols import (
|
| 19 |
+
PositionalSymbolRetriever,
|
| 20 |
+
RelationalSymbolicAttentionRetriever,
|
| 21 |
+
RelativePositionalSymbolRetriever,
|
| 22 |
+
SymbolicAttentionRetriever,
|
| 23 |
+
)
|
| 24 |
+
from .masks import build_decoder_attention_mask
|
| 25 |
+
from .mlm import MaskClassifier
|
| 26 |
+
from .transformer_components import FeedForward, PositionalEncoding, PositionalInfo, RMSNorm
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
class DatDecoderBlock(nn.Module):
|
| 30 |
+
def __init__(self, config: DatLMConfig):
|
| 31 |
+
super().__init__()
|
| 32 |
+
self.norm_first = config.norm_first
|
| 33 |
+
# input_dim is the block state width; hidden_dim is the total DAT width
|
| 34 |
+
# used with total_n_heads to derive the shared SA/RA head_dim.
|
| 35 |
+
self.sensory_attention = SelfAttention(
|
| 36 |
+
input_dim=config.hidden_dim,
|
| 37 |
+
n_heads=config.n_heads_sa,
|
| 38 |
+
hidden_dim=config.hidden_dim,
|
| 39 |
+
total_n_heads=config.total_n_heads,
|
| 40 |
+
dropout=config.dropout,
|
| 41 |
+
supports_relative=config.pe_type == "relative",
|
| 42 |
+
use_bias_qkv=config.use_bias_qkv,
|
| 43 |
+
use_bias_out=config.use_bias_out,
|
| 44 |
+
)
|
| 45 |
+
self.relational_attention = _build_relational_attention(config)
|
| 46 |
+
if config.share_attn_params:
|
| 47 |
+
self.sensory_attention.q_proj = self.relational_attention.q_proj
|
| 48 |
+
self.sensory_attention.k_proj = self.relational_attention.k_proj
|
| 49 |
+
self.feed_forward = FeedForward(
|
| 50 |
+
input_dim=config.hidden_dim,
|
| 51 |
+
hidden_dim=config.resolved_ffn_hidden_dim,
|
| 52 |
+
dropout=config.dropout,
|
| 53 |
+
activation=config.ffn_activation,
|
| 54 |
+
use_bias=config.use_bias_ffn,
|
| 55 |
+
)
|
| 56 |
+
self.norm1 = _create_norm(config.norm_type, config.hidden_dim)
|
| 57 |
+
self.norm2 = _create_norm(config.norm_type, config.hidden_dim)
|
| 58 |
+
self.dropout = nn.Dropout(config.dropout)
|
| 59 |
+
|
| 60 |
+
def forward(
|
| 61 |
+
self,
|
| 62 |
+
x: torch.Tensor,
|
| 63 |
+
symbol_retriever: nn.Module,
|
| 64 |
+
mask: torch.Tensor,
|
| 65 |
+
pos_info: Optional[PositionalInfo],
|
| 66 |
+
) -> torch.Tensor:
|
| 67 |
+
if self.norm_first:
|
| 68 |
+
normed_x = self.norm1(x)
|
| 69 |
+
# Retrieve symbols at the block boundary so pre-norm attention and
|
| 70 |
+
# input-dependent symbol retrieval use the same representation.
|
| 71 |
+
symbols = symbol_retriever(normed_x)
|
| 72 |
+
sensory_output, _ = self.sensory_attention(normed_x, mask=mask, pos_info=pos_info)
|
| 73 |
+
relational_output, _ = self.relational_attention(
|
| 74 |
+
normed_x,
|
| 75 |
+
symbols,
|
| 76 |
+
mask=mask,
|
| 77 |
+
pos_info=pos_info,
|
| 78 |
+
)
|
| 79 |
+
x = x + self.dropout(torch.cat((sensory_output, relational_output), dim=-1))
|
| 80 |
+
x = x + self.dropout(self.feed_forward(self.norm2(x)))
|
| 81 |
+
return x
|
| 82 |
+
|
| 83 |
+
symbols = symbol_retriever(x)
|
| 84 |
+
sensory_output, _ = self.sensory_attention(x, mask=mask, pos_info=pos_info)
|
| 85 |
+
relational_output, _ = self.relational_attention(
|
| 86 |
+
x,
|
| 87 |
+
symbols,
|
| 88 |
+
mask=mask,
|
| 89 |
+
pos_info=pos_info,
|
| 90 |
+
)
|
| 91 |
+
x = self.norm1(x + self.dropout(torch.cat((sensory_output, relational_output), dim=-1)))
|
| 92 |
+
x = self.norm2(x + self.dropout(self.feed_forward(x)))
|
| 93 |
+
return x
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
class DatDecoderLM(nn.Module):
|
| 97 |
+
def __init__(self, config: DatLMConfig):
|
| 98 |
+
super().__init__()
|
| 99 |
+
self.config = config
|
| 100 |
+
self.token_embeddings = nn.Embedding(config.vocab_size, config.hidden_dim)
|
| 101 |
+
self.embedding_dropout = nn.Dropout(config.dropout)
|
| 102 |
+
position_dim = (
|
| 103 |
+
config.hidden_dim if config.pe_type in {"sinusoidal", "learned", "none"}
|
| 104 |
+
else config.head_dim
|
| 105 |
+
)
|
| 106 |
+
self.position_encoder = PositionalEncoding(
|
| 107 |
+
embedding_dim=position_dim,
|
| 108 |
+
pe_type=config.pe_type,
|
| 109 |
+
max_len=config.max_seq_len,
|
| 110 |
+
theta=config.rope_theta,
|
| 111 |
+
max_rel_pos=config.max_rel_pos,
|
| 112 |
+
init_range=config.init_range,
|
| 113 |
+
)
|
| 114 |
+
self.symbol_retrievers = _build_symbol_retrievers(config)
|
| 115 |
+
self.layers = nn.ModuleList(DatDecoderBlock(config) for _ in range(config.n_layers))
|
| 116 |
+
self.final_norm = _create_norm(config.norm_type, config.hidden_dim)
|
| 117 |
+
self.lm_head = nn.Linear(config.hidden_dim, config.vocab_size, bias=False)
|
| 118 |
+
if config.mlm_head_enabled:
|
| 119 |
+
self.mlm_head = MaskClassifier(
|
| 120 |
+
hidden_dim=config.hidden_dim,
|
| 121 |
+
vocab_size=config.vocab_size,
|
| 122 |
+
norm_type=config.norm_type,
|
| 123 |
+
ffn_activation=config.ffn_activation,
|
| 124 |
+
use_bias_ffn=config.use_bias_ffn,
|
| 125 |
+
dropout=config.dropout,
|
| 126 |
+
word_embedding=self.lm_head.weight if not config.tie_lm_head else self.token_embeddings.weight,
|
| 127 |
+
)
|
| 128 |
+
self._init_weights()
|
| 129 |
+
|
| 130 |
+
@property
|
| 131 |
+
def device(self) -> torch.device:
|
| 132 |
+
return self.token_embeddings.weight.device
|
| 133 |
+
|
| 134 |
+
def _init_weights(self) -> None:
|
| 135 |
+
if self.config.init_scheme == "xavier_uniform":
|
| 136 |
+
nn.init.xavier_uniform_(
|
| 137 |
+
self.token_embeddings.weight,
|
| 138 |
+
gain=nn.init.calculate_gain("linear"),
|
| 139 |
+
)
|
| 140 |
+
if self.config.tie_lm_head:
|
| 141 |
+
self.lm_head.weight = self.token_embeddings.weight
|
| 142 |
+
else:
|
| 143 |
+
nn.init.xavier_uniform_(
|
| 144 |
+
self.lm_head.weight,
|
| 145 |
+
gain=nn.init.calculate_gain("linear"),
|
| 146 |
+
)
|
| 147 |
+
return
|
| 148 |
+
|
| 149 |
+
if self.config.init_scheme == "normal_0_02_scaled_projection":
|
| 150 |
+
self._init_normal_0_02_scaled_projection()
|
| 151 |
+
if self.config.tie_lm_head:
|
| 152 |
+
self.lm_head.weight = self.token_embeddings.weight
|
| 153 |
+
return
|
| 154 |
+
|
| 155 |
+
raise ValueError(f"Unsupported init_scheme: {self.config.init_scheme}")
|
| 156 |
+
|
| 157 |
+
def _init_normal_0_02_scaled_projection(self) -> None:
|
| 158 |
+
for module in self.modules():
|
| 159 |
+
if isinstance(module, nn.Linear):
|
| 160 |
+
nn.init.normal_(module.weight, mean=0.0, std=0.02)
|
| 161 |
+
if module.bias is not None:
|
| 162 |
+
nn.init.zeros_(module.bias)
|
| 163 |
+
elif isinstance(module, nn.Embedding):
|
| 164 |
+
nn.init.normal_(module.weight, mean=0.0, std=0.02)
|
| 165 |
+
|
| 166 |
+
for module in self.modules():
|
| 167 |
+
if isinstance(module, RelationalAttention):
|
| 168 |
+
nn.init.normal_(module.wr_proj, mean=0.0, std=0.02)
|
| 169 |
+
elif isinstance(module, SymbolicAttentionRetriever):
|
| 170 |
+
nn.init.normal_(module.template_features, mean=0.0, std=1.0)
|
| 171 |
+
nn.init.normal_(module.symbol_library, mean=0.0, std=1.0)
|
| 172 |
+
elif isinstance(module, RelativePositionalSymbolRetriever) and not module.rope:
|
| 173 |
+
nn.init.xavier_uniform_(module.position_encoder.rel_pos_embeddings_table.weight)
|
| 174 |
+
|
| 175 |
+
scaled_std = 0.02 / math.sqrt(2 * self.config.n_layers)
|
| 176 |
+
ffn_scaled_suffix = (
|
| 177 |
+
"feed_forward.w_up.weight"
|
| 178 |
+
if self.config.ffn_activation == "swiglu"
|
| 179 |
+
else "feed_forward.linear2.weight"
|
| 180 |
+
)
|
| 181 |
+
for name, parameter in self.named_parameters():
|
| 182 |
+
# Exclude the MLM head from depth-scaled init: its feed_forward
|
| 183 |
+
# down-projection shares the same suffix as decoder layers but
|
| 184 |
+
# should use the regular 0.02 initialization.
|
| 185 |
+
if name.startswith("mlm_head"):
|
| 186 |
+
continue
|
| 187 |
+
if name.endswith("o_proj.weight") or name.endswith(ffn_scaled_suffix):
|
| 188 |
+
nn.init.normal_(parameter, mean=0.0, std=scaled_std)
|
| 189 |
+
|
| 190 |
+
def _build_attention_mask(
|
| 191 |
+
self,
|
| 192 |
+
input_ids: torch.Tensor,
|
| 193 |
+
attention_mask: Optional[torch.Tensor] = None,
|
| 194 |
+
bidirectional: bool = False,
|
| 195 |
+
) -> torch.Tensor:
|
| 196 |
+
return build_decoder_attention_mask(
|
| 197 |
+
input_ids=input_ids,
|
| 198 |
+
pad_token_id=self.config.pad_token_id,
|
| 199 |
+
eos_token_id=self.config.eos_token_id,
|
| 200 |
+
sequence_boundary_policy=self.config.sequence_boundary_policy,
|
| 201 |
+
attention_mask=attention_mask,
|
| 202 |
+
segment_boundary_token_id=self.config.segment_boundary_token_id,
|
| 203 |
+
bidirectional=bidirectional,
|
| 204 |
+
)
|
| 205 |
+
|
| 206 |
+
def encode_for_objective(
|
| 207 |
+
self,
|
| 208 |
+
input_ids: torch.Tensor,
|
| 209 |
+
attention_mask: Optional[torch.Tensor] = None,
|
| 210 |
+
bidirectional: bool = False,
|
| 211 |
+
) -> tuple[torch.Tensor, torch.Tensor]:
|
| 212 |
+
if input_ids.dim() != 2:
|
| 213 |
+
raise ValueError(f"input_ids must be rank-2 [batch, seq], got shape {tuple(input_ids.shape)}")
|
| 214 |
+
if input_ids.shape[1] > self.config.max_seq_len:
|
| 215 |
+
raise ValueError(
|
| 216 |
+
f"Sequence length {input_ids.shape[1]} exceeds max_seq_len {self.config.max_seq_len}"
|
| 217 |
+
)
|
| 218 |
+
|
| 219 |
+
|
| 220 |
+
mask = self._build_attention_mask(input_ids, attention_mask, bidirectional=bidirectional)
|
| 221 |
+
token_embeddings = self.token_embeddings(input_ids)
|
| 222 |
+
hidden_states = token_embeddings
|
| 223 |
+
pos_info = self.position_encoder.get_positional_info(input_ids.shape[1], input_ids.device)
|
| 224 |
+
if pos_info.apply_to_embeddings:
|
| 225 |
+
if pos_info.embeddings is None:
|
| 226 |
+
raise ValueError(f"Embedding-level pe_type {pos_info.pe_type} did not provide embeddings.")
|
| 227 |
+
hidden_states = hidden_states + pos_info.embeddings.unsqueeze(0)
|
| 228 |
+
hidden_states = self.embedding_dropout(hidden_states)
|
| 229 |
+
|
| 230 |
+
for symbol_retriever, layer in zip(self.symbol_retrievers, self.layers):
|
| 231 |
+
hidden_states = layer(
|
| 232 |
+
hidden_states,
|
| 233 |
+
symbol_retriever=symbol_retriever,
|
| 234 |
+
mask=mask,
|
| 235 |
+
pos_info=pos_info,
|
| 236 |
+
)
|
| 237 |
+
|
| 238 |
+
hidden_states = self.final_norm(hidden_states)
|
| 239 |
+
return token_embeddings, hidden_states
|
| 240 |
+
|
| 241 |
+
def forward(
|
| 242 |
+
self,
|
| 243 |
+
input_ids: torch.Tensor,
|
| 244 |
+
targets: Optional[torch.Tensor] = None,
|
| 245 |
+
attention_mask: Optional[torch.Tensor] = None,
|
| 246 |
+
) -> tuple[torch.Tensor, Optional[torch.Tensor]]:
|
| 247 |
+
_, hidden_states = self.encode_for_objective(input_ids, attention_mask=attention_mask)
|
| 248 |
+
logits = self.lm_head(hidden_states)
|
| 249 |
+
|
| 250 |
+
loss = None
|
| 251 |
+
if targets is not None:
|
| 252 |
+
if targets.shape != input_ids.shape:
|
| 253 |
+
raise ValueError(
|
| 254 |
+
f"targets shape must match input_ids shape, got {tuple(targets.shape)} "
|
| 255 |
+
f"vs {tuple(input_ids.shape)}"
|
| 256 |
+
)
|
| 257 |
+
loss = F.cross_entropy(
|
| 258 |
+
logits.reshape(-1, logits.size(-1)),
|
| 259 |
+
targets.reshape(-1),
|
| 260 |
+
ignore_index=-1,
|
| 261 |
+
)
|
| 262 |
+
|
| 263 |
+
return logits, loss
|
| 264 |
+
|
| 265 |
+
def forward_mlm(
|
| 266 |
+
self,
|
| 267 |
+
input_ids: torch.Tensor,
|
| 268 |
+
attention_mask: Optional[torch.Tensor] = None,
|
| 269 |
+
) -> torch.Tensor:
|
| 270 |
+
"""Encode bidirectionally and apply the MLM head.
|
| 271 |
+
|
| 272 |
+
Args:
|
| 273 |
+
input_ids: Masked input token ids [batch, seq].
|
| 274 |
+
attention_mask: Validity mask [batch, seq].
|
| 275 |
+
|
| 276 |
+
Returns:
|
| 277 |
+
MLM logits at every position [batch, seq, vocab].
|
| 278 |
+
"""
|
| 279 |
+
if not self.config.mlm_head_enabled:
|
| 280 |
+
raise ValueError(
|
| 281 |
+
"forward_mlm requires mlm_head_enabled=True; "
|
| 282 |
+
"the MLM head is not instantiated."
|
| 283 |
+
)
|
| 284 |
+
_, hidden_states = self.encode_for_objective(
|
| 285 |
+
input_ids, attention_mask=attention_mask, bidirectional=True
|
| 286 |
+
)
|
| 287 |
+
return self.mlm_head(hidden_states)
|
| 288 |
+
|
| 289 |
+
@torch.no_grad()
|
| 290 |
+
def generate(
|
| 291 |
+
self,
|
| 292 |
+
input_ids: torch.Tensor,
|
| 293 |
+
attention_mask: Optional[torch.Tensor] = None,
|
| 294 |
+
max_new_tokens: int = 100,
|
| 295 |
+
temperature: float = 1.0,
|
| 296 |
+
top_k: Optional[int] = None,
|
| 297 |
+
do_sample: bool = True,
|
| 298 |
+
eos_token_id: Optional[int] = None,
|
| 299 |
+
) -> torch.Tensor:
|
| 300 |
+
if temperature <= 0.0:
|
| 301 |
+
raise ValueError(f"temperature must be positive, got {temperature}")
|
| 302 |
+
if max_new_tokens < 0:
|
| 303 |
+
raise ValueError(f"max_new_tokens must be non-negative, got {max_new_tokens}")
|
| 304 |
+
if top_k is not None and top_k <= 0:
|
| 305 |
+
raise ValueError(f"top_k must be positive when provided, got {top_k}")
|
| 306 |
+
if eos_token_id is None:
|
| 307 |
+
eos_token_id = self.config.eos_token_id
|
| 308 |
+
|
| 309 |
+
if attention_mask is None:
|
| 310 |
+
generated_sequences = [row.clone() for row in input_ids]
|
| 311 |
+
else:
|
| 312 |
+
current_attention_mask = attention_mask.bool()
|
| 313 |
+
generated_sequences = []
|
| 314 |
+
for row, row_mask in zip(input_ids, current_attention_mask):
|
| 315 |
+
generated_sequence = row[row_mask]
|
| 316 |
+
if generated_sequence.numel() == 0:
|
| 317 |
+
raise ValueError("Each input row must contain at least one unmasked token for generation.")
|
| 318 |
+
generated_sequences.append(generated_sequence)
|
| 319 |
+
|
| 320 |
+
finished = torch.tensor(
|
| 321 |
+
[sequence[-1].item() == eos_token_id for sequence in generated_sequences],
|
| 322 |
+
dtype=torch.bool,
|
| 323 |
+
device=input_ids.device,
|
| 324 |
+
)
|
| 325 |
+
|
| 326 |
+
for _ in range(max_new_tokens):
|
| 327 |
+
context_sequences = [sequence[-self.config.max_seq_len :] for sequence in generated_sequences]
|
| 328 |
+
context_ids = pad_sequence(
|
| 329 |
+
context_sequences,
|
| 330 |
+
batch_first=True,
|
| 331 |
+
padding_value=self.config.pad_token_id,
|
| 332 |
+
)
|
| 333 |
+
context_mask = pad_sequence(
|
| 334 |
+
[
|
| 335 |
+
torch.ones(sequence.shape[0], dtype=torch.bool, device=input_ids.device)
|
| 336 |
+
for sequence in context_sequences
|
| 337 |
+
],
|
| 338 |
+
batch_first=True,
|
| 339 |
+
padding_value=False,
|
| 340 |
+
)
|
| 341 |
+
logits, _ = self(context_ids, attention_mask=context_mask)
|
| 342 |
+
last_positions = context_mask.long().sum(dim=1) - 1
|
| 343 |
+
batch_indices = torch.arange(logits.shape[0], device=logits.device)
|
| 344 |
+
next_token_logits = logits[batch_indices, last_positions, :] / temperature
|
| 345 |
+
|
| 346 |
+
if top_k is not None:
|
| 347 |
+
k = min(top_k, next_token_logits.size(-1))
|
| 348 |
+
top_values, _ = torch.topk(next_token_logits, k=k)
|
| 349 |
+
cutoff = top_values[:, -1].unsqueeze(-1)
|
| 350 |
+
next_token_logits = next_token_logits.masked_fill(next_token_logits < cutoff, float("-inf"))
|
| 351 |
+
|
| 352 |
+
if do_sample:
|
| 353 |
+
probs = F.softmax(next_token_logits, dim=-1)
|
| 354 |
+
next_token = torch.multinomial(probs, num_samples=1)
|
| 355 |
+
else:
|
| 356 |
+
next_token = torch.argmax(next_token_logits, dim=-1, keepdim=True)
|
| 357 |
+
|
| 358 |
+
for row_index in range(next_token.shape[0]):
|
| 359 |
+
if finished[row_index]:
|
| 360 |
+
continue
|
| 361 |
+
generated_sequences[row_index] = torch.cat(
|
| 362 |
+
[generated_sequences[row_index], next_token[row_index]],
|
| 363 |
+
)
|
| 364 |
+
if next_token[row_index, 0].item() == eos_token_id:
|
| 365 |
+
finished[row_index] = True
|
| 366 |
+
|
| 367 |
+
if torch.all(finished):
|
| 368 |
+
break
|
| 369 |
+
|
| 370 |
+
return pad_sequence(
|
| 371 |
+
generated_sequences,
|
| 372 |
+
batch_first=True,
|
| 373 |
+
padding_value=self.config.pad_token_id,
|
| 374 |
+
)
|
| 375 |
+
|
| 376 |
+
|
| 377 |
+
def _build_symbol_retrievers(config: DatLMConfig) -> nn.ModuleList:
|
| 378 |
+
retriever = _build_symbol_retriever(config)
|
| 379 |
+
if config.shared_symbol_retriever:
|
| 380 |
+
return nn.ModuleList([retriever] * config.n_layers)
|
| 381 |
+
return nn.ModuleList(_build_symbol_retriever(config) for _ in range(config.n_layers))
|
| 382 |
+
|
| 383 |
+
|
| 384 |
+
def _build_symbol_retriever(config: DatLMConfig) -> nn.Module:
|
| 385 |
+
if config.symbol_retrieval == "symbolic":
|
| 386 |
+
return SymbolicAttentionRetriever(
|
| 387 |
+
hidden_dim=config.hidden_dim,
|
| 388 |
+
symbol_dim=config.resolved_symbol_dim,
|
| 389 |
+
n_symbols=config.resolved_n_symbols,
|
| 390 |
+
n_heads=config.resolved_symbolic_attn_n_heads,
|
| 391 |
+
dropout=config.dropout,
|
| 392 |
+
use_bias=config.symbolic_use_bias,
|
| 393 |
+
)
|
| 394 |
+
if config.symbol_retrieval == "positional":
|
| 395 |
+
return PositionalSymbolRetriever(
|
| 396 |
+
symbol_dim=config.resolved_symbol_dim,
|
| 397 |
+
max_len=config.max_seq_len,
|
| 398 |
+
sinusoidal=config.positional_symbols_sinusoidal,
|
| 399 |
+
)
|
| 400 |
+
if config.symbol_retrieval == "relative":
|
| 401 |
+
max_rel_distance = config.max_rel_pos if config.max_rel_pos is not None else max(1, config.max_seq_len // 2)
|
| 402 |
+
return RelativePositionalSymbolRetriever(
|
| 403 |
+
symbol_dim=config.resolved_symbol_dim,
|
| 404 |
+
max_rel_distance=max_rel_distance,
|
| 405 |
+
rope=config.relative_symbols_rope,
|
| 406 |
+
theta=config.rope_theta,
|
| 407 |
+
)
|
| 408 |
+
if config.symbol_retrieval == "relsymbolic":
|
| 409 |
+
return RelationalSymbolicAttentionRetriever(
|
| 410 |
+
hidden_dim=config.hidden_dim,
|
| 411 |
+
symbol_dim=config.resolved_symbol_dim,
|
| 412 |
+
rel_n_heads=config.relsymbolic_rel_n_heads,
|
| 413 |
+
symbolic_attn_n_heads=config.relsymbolic_symbolic_attn_n_heads,
|
| 414 |
+
n_symbols=config.resolved_n_symbols,
|
| 415 |
+
neighborhood_size=config.relsymbolic_neighborhood_size,
|
| 416 |
+
include_self=config.relsymbolic_include_self,
|
| 417 |
+
normalize_rels=config.relsymbolic_normalize_rels,
|
| 418 |
+
dropout=config.relsymbolic_dropout,
|
| 419 |
+
trainable_symbols=config.relsymbolic_trainable_symbols,
|
| 420 |
+
rel_scale=config.relsymbolic_rel_scale,
|
| 421 |
+
symbolic_attn_scale=config.relsymbolic_symbolic_attn_scale,
|
| 422 |
+
use_bias=config.relsymbolic_use_bias,
|
| 423 |
+
)
|
| 424 |
+
raise ValueError(f"Unsupported symbol_retrieval: {config.symbol_retrieval}")
|
| 425 |
+
|
| 426 |
+
|
| 427 |
+
def _build_relational_attention(config: DatLMConfig) -> nn.Module:
|
| 428 |
+
use_relative_symbols = config.symbol_retrieval == "relative"
|
| 429 |
+
if config.ra_type == "ra":
|
| 430 |
+
return RelationalAttention(
|
| 431 |
+
hidden_dim=config.hidden_dim,
|
| 432 |
+
symbol_dim=config.resolved_symbol_dim,
|
| 433 |
+
n_heads=config.n_heads_ra,
|
| 434 |
+
total_n_heads=config.total_n_heads,
|
| 435 |
+
n_relations=config.resolved_ra_n_relations,
|
| 436 |
+
dropout=config.dropout,
|
| 437 |
+
rel_activation=config.ra_rel_activation,
|
| 438 |
+
symmetric_rels=config.ra_symmetric_rels,
|
| 439 |
+
use_relative_positional_symbols=use_relative_symbols,
|
| 440 |
+
use_bias_qkv=config.use_bias_qkv,
|
| 441 |
+
use_bias_out=config.use_bias_out,
|
| 442 |
+
)
|
| 443 |
+
if config.ra_type == "rca":
|
| 444 |
+
return RelationalCrossAttention(
|
| 445 |
+
hidden_dim=config.hidden_dim,
|
| 446 |
+
symbol_dim=config.resolved_symbol_dim,
|
| 447 |
+
n_heads=config.n_heads_ra,
|
| 448 |
+
total_n_heads=config.total_n_heads,
|
| 449 |
+
dropout=config.dropout,
|
| 450 |
+
activation=config.ra_rel_activation,
|
| 451 |
+
use_relative_positional_symbols=use_relative_symbols,
|
| 452 |
+
use_bias_qkv=config.use_bias_qkv,
|
| 453 |
+
use_bias_out=config.use_bias_out,
|
| 454 |
+
)
|
| 455 |
+
if config.ra_type == "disrca":
|
| 456 |
+
return DisentangledRelationalCrossAttention(
|
| 457 |
+
hidden_dim=config.hidden_dim,
|
| 458 |
+
symbol_dim=config.resolved_symbol_dim,
|
| 459 |
+
n_heads=config.n_heads_ra,
|
| 460 |
+
total_n_heads=config.total_n_heads,
|
| 461 |
+
dropout=config.dropout,
|
| 462 |
+
rel_activation=config.ra_rel_activation,
|
| 463 |
+
use_relative_positional_symbols=use_relative_symbols,
|
| 464 |
+
use_bias_qkv=config.use_bias_qkv,
|
| 465 |
+
use_bias_out=config.use_bias_out,
|
| 466 |
+
)
|
| 467 |
+
raise ValueError(f"Unsupported ra_type: {config.ra_type}")
|
| 468 |
+
|
| 469 |
+
|
| 470 |
+
def _create_norm(norm_type: str, hidden_dim: int) -> nn.Module:
|
| 471 |
+
if norm_type == "layernorm":
|
| 472 |
+
return nn.LayerNorm(hidden_dim)
|
| 473 |
+
if norm_type == "rmsnorm":
|
| 474 |
+
return RMSNorm(hidden_dim)
|
| 475 |
+
raise ValueError(f"Unsupported norm_type: {norm_type}")
|
dat_symbols.py
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Symbol retrieval modules for the owned DAT backend."""
|
| 2 |
+
|
| 3 |
+
import math
|
| 4 |
+
|
| 5 |
+
import torch
|
| 6 |
+
import torch.nn as nn
|
| 7 |
+
import torch.nn.functional as F
|
| 8 |
+
|
| 9 |
+
from .transformer_components import PositionalEncoding
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
class SymbolicAttentionRetriever(nn.Module):
|
| 13 |
+
def __init__(
|
| 14 |
+
self,
|
| 15 |
+
hidden_dim: int,
|
| 16 |
+
symbol_dim: int,
|
| 17 |
+
n_symbols: int,
|
| 18 |
+
n_heads: int,
|
| 19 |
+
dropout: float = 0.0,
|
| 20 |
+
trainable_symbols: bool = True,
|
| 21 |
+
scale: float | None = None,
|
| 22 |
+
use_bias: bool = False,
|
| 23 |
+
):
|
| 24 |
+
super().__init__()
|
| 25 |
+
if hidden_dim % n_heads != 0:
|
| 26 |
+
raise ValueError(f"hidden_dim ({hidden_dim}) must be divisible by n_heads ({n_heads})")
|
| 27 |
+
if symbol_dim % n_heads != 0:
|
| 28 |
+
raise ValueError(f"symbol_dim ({symbol_dim}) must be divisible by n_heads ({n_heads})")
|
| 29 |
+
if scale is not None and scale <= 0.0:
|
| 30 |
+
raise ValueError(f"scale must be positive when provided, got {scale}")
|
| 31 |
+
|
| 32 |
+
self.hidden_dim = hidden_dim
|
| 33 |
+
self.symbol_dim = symbol_dim
|
| 34 |
+
self.n_symbols = n_symbols
|
| 35 |
+
self.n_heads = n_heads
|
| 36 |
+
self.head_dim = hidden_dim // n_heads
|
| 37 |
+
self.symbol_head_dim = symbol_dim // n_heads
|
| 38 |
+
self.scale = 1.0 / math.sqrt(self.head_dim) if scale is None else scale
|
| 39 |
+
|
| 40 |
+
self.q_proj = nn.Linear(hidden_dim, hidden_dim, bias=use_bias)
|
| 41 |
+
self.template_features = nn.Parameter(torch.empty(n_symbols, hidden_dim))
|
| 42 |
+
self.symbol_library = nn.Parameter(
|
| 43 |
+
torch.empty(n_symbols, symbol_dim),
|
| 44 |
+
requires_grad=trainable_symbols,
|
| 45 |
+
)
|
| 46 |
+
self.dropout = nn.Dropout(dropout)
|
| 47 |
+
self._init_weights()
|
| 48 |
+
|
| 49 |
+
def _init_weights(self) -> None:
|
| 50 |
+
nn.init.normal_(self.template_features, mean=0.0, std=0.9)
|
| 51 |
+
nn.init.normal_(self.symbol_library, mean=0.0, std=0.9)
|
| 52 |
+
|
| 53 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 54 |
+
batch_size, seq_len, _ = x.shape
|
| 55 |
+
|
| 56 |
+
queries = self.q_proj(x)
|
| 57 |
+
queries = queries.view(batch_size, seq_len, self.n_heads, self.head_dim)
|
| 58 |
+
queries = queries.transpose(1, 2)
|
| 59 |
+
|
| 60 |
+
keys = self.template_features.view(self.n_symbols, self.n_heads, self.head_dim)
|
| 61 |
+
keys = keys.transpose(0, 1).unsqueeze(0).expand(batch_size, -1, -1, -1)
|
| 62 |
+
|
| 63 |
+
values = self.symbol_library.view(self.n_symbols, self.n_heads, self.symbol_head_dim)
|
| 64 |
+
values = values.transpose(0, 1).unsqueeze(0).expand(batch_size, -1, -1, -1)
|
| 65 |
+
|
| 66 |
+
scores = torch.matmul(queries, keys.transpose(-2, -1)) * self.scale
|
| 67 |
+
weights = F.softmax(scores, dim=-1)
|
| 68 |
+
weights = self.dropout(weights)
|
| 69 |
+
retrieved = torch.matmul(weights, values)
|
| 70 |
+
retrieved = retrieved.transpose(1, 2).contiguous()
|
| 71 |
+
return retrieved.view(batch_size, seq_len, self.symbol_dim)
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
class PositionalSymbolRetriever(nn.Module):
|
| 75 |
+
def __init__(
|
| 76 |
+
self,
|
| 77 |
+
symbol_dim: int,
|
| 78 |
+
max_len: int,
|
| 79 |
+
sinusoidal: bool = False,
|
| 80 |
+
):
|
| 81 |
+
super().__init__()
|
| 82 |
+
self.symbol_dim = symbol_dim
|
| 83 |
+
self.max_len = max_len
|
| 84 |
+
self.sinusoidal = sinusoidal
|
| 85 |
+
pe_type = "sinusoidal" if sinusoidal else "learned"
|
| 86 |
+
self.position_encoder = PositionalEncoding(
|
| 87 |
+
embedding_dim=symbol_dim,
|
| 88 |
+
pe_type=pe_type,
|
| 89 |
+
max_len=max_len,
|
| 90 |
+
)
|
| 91 |
+
|
| 92 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 93 |
+
batch_size, seq_len, _ = x.shape
|
| 94 |
+
pos_info = self.position_encoder.get_positional_info(seq_len, x.device)
|
| 95 |
+
if pos_info.embeddings is None:
|
| 96 |
+
raise ValueError(f"Positional symbol retrieval produced no embeddings for seq_len={seq_len}")
|
| 97 |
+
return pos_info.embeddings.unsqueeze(0).expand(batch_size, -1, -1)
|
| 98 |
+
|
| 99 |
+
|
| 100 |
+
class RelativePositionalSymbolRetriever(nn.Module):
|
| 101 |
+
def __init__(
|
| 102 |
+
self,
|
| 103 |
+
symbol_dim: int,
|
| 104 |
+
max_rel_distance: int,
|
| 105 |
+
rope: bool = False,
|
| 106 |
+
theta: float = 10000.0,
|
| 107 |
+
):
|
| 108 |
+
super().__init__()
|
| 109 |
+
if rope and symbol_dim % 2 != 0:
|
| 110 |
+
raise ValueError(f"RoPE relative symbols require even symbol_dim, got {symbol_dim}")
|
| 111 |
+
if theta <= 0.0:
|
| 112 |
+
raise ValueError(f"theta must be positive, got {theta}")
|
| 113 |
+
self.symbol_dim = symbol_dim
|
| 114 |
+
self.max_rel_distance = max_rel_distance
|
| 115 |
+
self.rope = rope
|
| 116 |
+
self.theta = theta
|
| 117 |
+
if rope:
|
| 118 |
+
self.register_buffer("_rope_relative_cache", torch.empty(0), persistent=False)
|
| 119 |
+
else:
|
| 120 |
+
self.position_encoder = PositionalEncoding(
|
| 121 |
+
embedding_dim=symbol_dim,
|
| 122 |
+
pe_type="relative",
|
| 123 |
+
max_len=max_rel_distance * 2,
|
| 124 |
+
max_rel_pos=max_rel_distance,
|
| 125 |
+
)
|
| 126 |
+
|
| 127 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 128 |
+
seq_len = x.shape[1]
|
| 129 |
+
if self.rope:
|
| 130 |
+
return self._rope_relative_symbols(seq_len, x.device, x.dtype)
|
| 131 |
+
|
| 132 |
+
pos_info = self.position_encoder.get_positional_info(seq_len, x.device)
|
| 133 |
+
if pos_info.rel_embeddings is None:
|
| 134 |
+
raise ValueError(f"Relative symbol retrieval produced no embeddings for seq_len={seq_len}")
|
| 135 |
+
return pos_info.rel_embeddings
|
| 136 |
+
|
| 137 |
+
def _rope_relative_symbols(
|
| 138 |
+
self,
|
| 139 |
+
seq_len: int,
|
| 140 |
+
device: torch.device,
|
| 141 |
+
dtype: torch.dtype,
|
| 142 |
+
) -> torch.Tensor:
|
| 143 |
+
cached = self._rope_relative_cache
|
| 144 |
+
if (
|
| 145 |
+
cached.shape[0] >= seq_len
|
| 146 |
+
and cached.device == device
|
| 147 |
+
and cached.dtype == dtype
|
| 148 |
+
):
|
| 149 |
+
return cached[:seq_len, :seq_len, :]
|
| 150 |
+
|
| 151 |
+
positions = torch.arange(seq_len, device=device)
|
| 152 |
+
distances = positions[None, :] - positions[:, None]
|
| 153 |
+
if self.max_rel_distance is not None:
|
| 154 |
+
distances = torch.clamp(distances, -self.max_rel_distance, self.max_rel_distance)
|
| 155 |
+
inv_freq = PositionalEncoding._rope_inv_freq(self.symbol_dim, self.theta, device=device)
|
| 156 |
+
phases = distances.to(torch.float32).unsqueeze(-1) * inv_freq
|
| 157 |
+
symbols = torch.empty(seq_len, seq_len, self.symbol_dim, device=device)
|
| 158 |
+
symbols[..., 0::2] = torch.cos(phases)
|
| 159 |
+
symbols[..., 1::2] = torch.sin(phases)
|
| 160 |
+
self._rope_relative_cache = symbols.to(dtype=dtype)
|
| 161 |
+
return self._rope_relative_cache
|
| 162 |
+
|
| 163 |
+
|
| 164 |
+
class RelationalSymbolicAttentionRetriever(nn.Module):
|
| 165 |
+
def __init__(
|
| 166 |
+
self,
|
| 167 |
+
hidden_dim: int,
|
| 168 |
+
symbol_dim: int,
|
| 169 |
+
rel_n_heads: int,
|
| 170 |
+
symbolic_attn_n_heads: int,
|
| 171 |
+
n_symbols: int,
|
| 172 |
+
neighborhood_size: int = 2,
|
| 173 |
+
include_self: bool = False,
|
| 174 |
+
normalize_rels: bool = True,
|
| 175 |
+
dropout: float = 0.0,
|
| 176 |
+
trainable_symbols: bool = True,
|
| 177 |
+
rel_scale: float | None = None,
|
| 178 |
+
symbolic_attn_scale: float | None = None,
|
| 179 |
+
use_bias: bool = False,
|
| 180 |
+
):
|
| 181 |
+
super().__init__()
|
| 182 |
+
if hidden_dim % rel_n_heads != 0:
|
| 183 |
+
raise ValueError(
|
| 184 |
+
f"hidden_dim ({hidden_dim}) must be divisible by rel_n_heads ({rel_n_heads})"
|
| 185 |
+
)
|
| 186 |
+
if rel_scale is not None and rel_scale <= 0.0:
|
| 187 |
+
raise ValueError(f"rel_scale must be positive when provided, got {rel_scale}")
|
| 188 |
+
if symbolic_attn_scale is not None and symbolic_attn_scale <= 0.0:
|
| 189 |
+
raise ValueError(
|
| 190 |
+
"symbolic_attn_scale must be positive when provided, "
|
| 191 |
+
f"got {symbolic_attn_scale}"
|
| 192 |
+
)
|
| 193 |
+
|
| 194 |
+
self.hidden_dim = hidden_dim
|
| 195 |
+
self.symbol_dim = symbol_dim
|
| 196 |
+
self.rel_n_heads = rel_n_heads
|
| 197 |
+
self.neighborhood_size = neighborhood_size
|
| 198 |
+
self.include_self = include_self
|
| 199 |
+
self.normalize_rels = normalize_rels
|
| 200 |
+
self.neighborhood_dim = neighborhood_size + (1 if include_self else 0)
|
| 201 |
+
self.rel_feature_dim = rel_n_heads * self.neighborhood_dim
|
| 202 |
+
rel_head_dim = hidden_dim // rel_n_heads
|
| 203 |
+
self.rel_scale = 1.0 / math.sqrt(rel_head_dim) if rel_scale is None else rel_scale
|
| 204 |
+
|
| 205 |
+
self.q_proj = nn.Linear(hidden_dim, hidden_dim, bias=use_bias)
|
| 206 |
+
self.k_proj = nn.Linear(hidden_dim, hidden_dim, bias=use_bias)
|
| 207 |
+
self.rel_to_hidden = nn.Linear(self.rel_feature_dim, hidden_dim, bias=True)
|
| 208 |
+
self.symbolic_attention = SymbolicAttentionRetriever(
|
| 209 |
+
hidden_dim=hidden_dim,
|
| 210 |
+
symbol_dim=symbol_dim,
|
| 211 |
+
n_symbols=n_symbols,
|
| 212 |
+
n_heads=symbolic_attn_n_heads,
|
| 213 |
+
dropout=dropout,
|
| 214 |
+
trainable_symbols=trainable_symbols,
|
| 215 |
+
scale=symbolic_attn_scale,
|
| 216 |
+
use_bias=use_bias,
|
| 217 |
+
)
|
| 218 |
+
self._init_weights()
|
| 219 |
+
|
| 220 |
+
def _init_weights(self) -> None:
|
| 221 |
+
nn.init.xavier_uniform_(self.q_proj.weight)
|
| 222 |
+
nn.init.xavier_uniform_(self.k_proj.weight)
|
| 223 |
+
if self.q_proj.bias is not None:
|
| 224 |
+
nn.init.zeros_(self.q_proj.bias)
|
| 225 |
+
if self.k_proj.bias is not None:
|
| 226 |
+
nn.init.zeros_(self.k_proj.bias)
|
| 227 |
+
nn.init.xavier_uniform_(self.rel_to_hidden.weight)
|
| 228 |
+
nn.init.zeros_(self.rel_to_hidden.bias)
|
| 229 |
+
|
| 230 |
+
def _compute_neighborhood_indices(self, seq_len: int, device: torch.device) -> torch.Tensor:
|
| 231 |
+
positions = torch.arange(seq_len, device=device).unsqueeze(1)
|
| 232 |
+
# Decoder-LM adaptation: DSSL uses bidirectional neighborhoods, but this
|
| 233 |
+
# owned backend must keep symbol retrieval causal. Keep the old causal
|
| 234 |
+
# neighborhood order: current/immediate-past positions before older ones.
|
| 235 |
+
if self.include_self:
|
| 236 |
+
offsets = torch.arange(0, self.neighborhood_size + 1, device=device).unsqueeze(0)
|
| 237 |
+
else:
|
| 238 |
+
offsets = torch.arange(1, self.neighborhood_size + 1, device=device).unsqueeze(0)
|
| 239 |
+
return (positions - offsets).clamp(0, seq_len - 1)
|
| 240 |
+
|
| 241 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 242 |
+
batch_size, seq_len, _ = x.shape
|
| 243 |
+
head_dim = self.hidden_dim // self.rel_n_heads
|
| 244 |
+
|
| 245 |
+
queries = self.q_proj(x).view(batch_size, seq_len, self.rel_n_heads, head_dim)
|
| 246 |
+
keys = self.k_proj(x).view(batch_size, seq_len, self.rel_n_heads, head_dim)
|
| 247 |
+
queries = queries.transpose(1, 2)
|
| 248 |
+
keys = keys.transpose(1, 2)
|
| 249 |
+
|
| 250 |
+
neighbor_indices = self._compute_neighborhood_indices(seq_len, x.device)
|
| 251 |
+
neighborhood_keys = keys[:, :, neighbor_indices]
|
| 252 |
+
neighborhood_relations = torch.einsum("bhid,bhijd->bhij", queries, neighborhood_keys)
|
| 253 |
+
if self.normalize_rels:
|
| 254 |
+
neighborhood_relations = F.softmax(neighborhood_relations * self.rel_scale, dim=-1)
|
| 255 |
+
|
| 256 |
+
neighborhood_relations = neighborhood_relations.permute(0, 2, 3, 1)
|
| 257 |
+
neighborhood_relations = neighborhood_relations.contiguous().view(
|
| 258 |
+
batch_size,
|
| 259 |
+
seq_len,
|
| 260 |
+
self.rel_feature_dim,
|
| 261 |
+
)
|
| 262 |
+
relational_features = self.rel_to_hidden(neighborhood_relations)
|
| 263 |
+
return self.symbolic_attention(relational_features)
|
masks.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Attention-mask helpers shared by repo-owned decoder LMs."""
|
| 2 |
+
|
| 3 |
+
from typing import Optional
|
| 4 |
+
|
| 5 |
+
import torch
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def build_decoder_attention_mask(
|
| 9 |
+
input_ids: torch.Tensor,
|
| 10 |
+
pad_token_id: int,
|
| 11 |
+
eos_token_id: int,
|
| 12 |
+
sequence_boundary_policy: str,
|
| 13 |
+
attention_mask: Optional[torch.Tensor] = None,
|
| 14 |
+
segment_boundary_token_id: Optional[int] = None,
|
| 15 |
+
bidirectional: bool = False,
|
| 16 |
+
) -> torch.Tensor:
|
| 17 |
+
if input_ids.ndim != 2:
|
| 18 |
+
raise ValueError(
|
| 19 |
+
"input_ids must be rank-2 [batch, seq], "
|
| 20 |
+
f"got shape {tuple(input_ids.shape)}"
|
| 21 |
+
)
|
| 22 |
+
_, seq_len = input_ids.shape
|
| 23 |
+
if attention_mask is None:
|
| 24 |
+
valid_tokens = input_ids != pad_token_id
|
| 25 |
+
else:
|
| 26 |
+
valid_tokens = attention_mask.bool()
|
| 27 |
+
|
| 28 |
+
query_mask = valid_tokens.unsqueeze(2)
|
| 29 |
+
key_mask = valid_tokens.unsqueeze(1)
|
| 30 |
+
if bidirectional:
|
| 31 |
+
mask = query_mask & key_mask
|
| 32 |
+
else:
|
| 33 |
+
directionality = torch.tril(
|
| 34 |
+
torch.ones(seq_len, seq_len, dtype=torch.bool, device=input_ids.device)
|
| 35 |
+
).unsqueeze(0)
|
| 36 |
+
mask = directionality & query_mask & key_mask
|
| 37 |
+
|
| 38 |
+
if sequence_boundary_policy == "none":
|
| 39 |
+
return mask
|
| 40 |
+
if sequence_boundary_policy == "segment_document":
|
| 41 |
+
if segment_boundary_token_id is None:
|
| 42 |
+
raise ValueError(
|
| 43 |
+
"segment_boundary_token_id is required when "
|
| 44 |
+
"sequence_boundary_policy='segment_document'"
|
| 45 |
+
)
|
| 46 |
+
# The boundary token starts the next segment: cumsum increments on the
|
| 47 |
+
# boundary position, so the marker attends with the following tokens.
|
| 48 |
+
segment_ids = torch.cumsum(
|
| 49 |
+
input_ids == segment_boundary_token_id, dim=1
|
| 50 |
+
)
|
| 51 |
+
same_segment = segment_ids.unsqueeze(1) == segment_ids.unsqueeze(2)
|
| 52 |
+
return mask & same_segment
|
| 53 |
+
if sequence_boundary_policy != "eos_document":
|
| 54 |
+
raise ValueError(f"Unsupported sequence_boundary_policy: {sequence_boundary_policy}")
|
| 55 |
+
|
| 56 |
+
# Next-token training predicts EOS from the preceding document token.
|
| 57 |
+
# Once EOS is present as an input token, it starts the next segment so the
|
| 58 |
+
# following document is not predicted with prior-document context.
|
| 59 |
+
document_ids = torch.cumsum(input_ids == eos_token_id, dim=1)
|
| 60 |
+
same_document = document_ids.unsqueeze(1) == document_ids.unsqueeze(2)
|
| 61 |
+
return mask & same_document
|
mlm.py
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""MLM head for the hybrid MLM/CLM objective.
|
| 2 |
+
|
| 3 |
+
Ported from temp/gpt-bert-main/pretraining/model.py MaskClassifier, but
|
| 4 |
+
configurable to match the trunk's customizability:
|
| 5 |
+
- norm_type: "layernorm" or "rmsnorm" (reuses the existing field).
|
| 6 |
+
- ffn_activation: "gelu", "swiglu", "relu", or "identity" (reuses the
|
| 7 |
+
existing field). The head's nonlinearity follows this choice.
|
| 8 |
+
- use_bias_ffn: whether the linear layers in the head use bias.
|
| 9 |
+
- weight tying: the final linear is tied to the word embedding, matching
|
| 10 |
+
gpt-bert's MaskClassifier and our lm_head.
|
| 11 |
+
|
| 12 |
+
The head is controlled by model config (mlm_head_enabled) so checkpoint
|
| 13 |
+
loading and HF export remain strict and honest.
|
| 14 |
+
"""
|
| 15 |
+
|
| 16 |
+
import torch
|
| 17 |
+
import torch.nn as nn
|
| 18 |
+
|
| 19 |
+
from .transformer_components import RMSNorm, FeedForward
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def _create_norm(norm_type: str, hidden_dim: int) -> nn.Module:
|
| 23 |
+
if norm_type == "layernorm":
|
| 24 |
+
return nn.LayerNorm(hidden_dim)
|
| 25 |
+
if norm_type == "rmsnorm":
|
| 26 |
+
return RMSNorm(hidden_dim)
|
| 27 |
+
raise ValueError(f"Unsupported norm_type: {norm_type}")
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
class MaskClassifier(nn.Module):
|
| 31 |
+
"""MLM prediction head with configurable norm and activation.
|
| 32 |
+
|
| 33 |
+
Architecture (matching gpt-bert's MaskClassifier):
|
| 34 |
+
norm -> FeedForward (activation, bias configurable) -> norm -> dropout -> linear (tied)
|
| 35 |
+
|
| 36 |
+
The final linear is tied to the word embedding weight, matching
|
| 37 |
+
gpt-bert's MaskClassifier and our lm_head.
|
| 38 |
+
|
| 39 |
+
Args:
|
| 40 |
+
hidden_dim: Model hidden dimension.
|
| 41 |
+
vocab_size: Vocabulary size for the output projection.
|
| 42 |
+
norm_type: "layernorm" or "rmsnorm".
|
| 43 |
+
ffn_activation: "gelu", "swiglu", "relu", or "identity".
|
| 44 |
+
use_bias_ffn: Whether the intermediate linear layers use bias.
|
| 45 |
+
dropout: Dropout probability.
|
| 46 |
+
word_embedding: The word embedding weight to tie the final linear to.
|
| 47 |
+
"""
|
| 48 |
+
|
| 49 |
+
def __init__(
|
| 50 |
+
self,
|
| 51 |
+
hidden_dim: int,
|
| 52 |
+
vocab_size: int,
|
| 53 |
+
norm_type: str,
|
| 54 |
+
ffn_activation: str,
|
| 55 |
+
use_bias_ffn: bool,
|
| 56 |
+
dropout: float,
|
| 57 |
+
word_embedding: nn.Parameter,
|
| 58 |
+
):
|
| 59 |
+
super().__init__()
|
| 60 |
+
self.hidden_dim = hidden_dim
|
| 61 |
+
self.vocab_size = vocab_size
|
| 62 |
+
self.norm_type = norm_type
|
| 63 |
+
self.ffn_activation = ffn_activation
|
| 64 |
+
self.use_bias_ffn = use_bias_ffn
|
| 65 |
+
self.dropout_p = dropout
|
| 66 |
+
|
| 67 |
+
self.norm1 = _create_norm(norm_type, hidden_dim)
|
| 68 |
+
self.feed_forward = FeedForward(
|
| 69 |
+
input_dim=hidden_dim,
|
| 70 |
+
hidden_dim=hidden_dim,
|
| 71 |
+
dropout=dropout,
|
| 72 |
+
activation=ffn_activation,
|
| 73 |
+
use_bias=use_bias_ffn,
|
| 74 |
+
)
|
| 75 |
+
self.norm2 = _create_norm(norm_type, hidden_dim)
|
| 76 |
+
self.dropout = nn.Dropout(dropout)
|
| 77 |
+
self.linear_out = nn.Linear(hidden_dim, vocab_size, bias=False)
|
| 78 |
+
self.linear_out.weight = word_embedding
|
| 79 |
+
|
| 80 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 81 |
+
"""Apply the MLM head to contextualized embeddings.
|
| 82 |
+
|
| 83 |
+
Args:
|
| 84 |
+
x: Contextualized embeddings [batch, seq_len, hidden_dim] or
|
| 85 |
+
flattened [num_masked, hidden_dim].
|
| 86 |
+
|
| 87 |
+
Returns:
|
| 88 |
+
Logits [batch, seq_len, vocab_size] or [num_masked, vocab_size].
|
| 89 |
+
"""
|
| 90 |
+
x = self.norm1(x)
|
| 91 |
+
x = self.feed_forward(x)
|
| 92 |
+
x = self.norm2(x)
|
| 93 |
+
x = self.dropout(x)
|
| 94 |
+
x = self.linear_out(x)
|
| 95 |
+
return x
|
model.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:8af548664053b171d726ec05dd80f7bf2f9cb46933fcf2e27071d3cc29fa4531
|
| 3 |
+
size 1187885624
|
modeling_dat.py
ADDED
|
@@ -0,0 +1,290 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""HuggingFace PreTrainedModel wrapper for the dual-attention (DAT) decoder LM.
|
| 2 |
+
|
| 3 |
+
Source-of-truth copy. scripts/convert_dat_to_hf.py copies it into a generated HF
|
| 4 |
+
repository together with configuration_dat.py and the flattened model source
|
| 5 |
+
(dat_config.py, dat_core.py, dat_symbols.py, dat_lm.py, transformer_core.py,
|
| 6 |
+
transformer_components.py).
|
| 7 |
+
|
| 8 |
+
Why every model module is imported directly here: with trust_remote_code on a
|
| 9 |
+
local directory, transformers 4.46.3 only copies the entry module's *direct*
|
| 10 |
+
relative imports into its dynamic-module cache (it does not recurse). Importing
|
| 11 |
+
all flattened modules here forces every one of them to be copied, after which
|
| 12 |
+
their own relative imports resolve because the files are co-located.
|
| 13 |
+
"""
|
| 14 |
+
|
| 15 |
+
import collections
|
| 16 |
+
|
| 17 |
+
import torch
|
| 18 |
+
from transformers import PreTrainedModel
|
| 19 |
+
from transformers.modeling_outputs import BaseModelOutput, CausalLMOutput, MaskedLMOutput, SequenceClassifierOutput
|
| 20 |
+
from torch.nn import BCEWithLogitsLoss, CrossEntropyLoss, MSELoss
|
| 21 |
+
|
| 22 |
+
# Force every flattened module to be copied into the dynamic-module cache.
|
| 23 |
+
from .attention_config import SUPPORTED_SEQUENCE_BOUNDARY_POLICIES # noqa: F401
|
| 24 |
+
from .masks import build_decoder_attention_mask # noqa: F401
|
| 25 |
+
from .mlm import MaskClassifier # noqa: F401
|
| 26 |
+
from .transformer_components import FeedForward, PositionalEncoding # noqa: F401
|
| 27 |
+
from .transformer_core import SelfAttention # noqa: F401
|
| 28 |
+
from .dat_symbols import SymbolicAttentionRetriever # noqa: F401
|
| 29 |
+
from .dat_core import RelationalAttention # noqa: F401
|
| 30 |
+
from .dat_config import DatLMConfig
|
| 31 |
+
from .dat_lm import DatDecoderLM
|
| 32 |
+
from .configuration_dat import DatConfig, DAT_LM_FIELDS
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def _refresh_rope_buffers(model: DatDecoderLM, rope_theta: float) -> None:
|
| 36 |
+
pe = model.position_encoder
|
| 37 |
+
if hasattr(pe, "pe_type") and pe.pe_type == "rope":
|
| 38 |
+
cos, sin = pe._precompute_rope_freqs(pe.embedding_dim, pe.max_len, rope_theta)
|
| 39 |
+
pe.register_buffer("rope_cos", cos, persistent=False)
|
| 40 |
+
pe.register_buffer("rope_sin", sin, persistent=False)
|
| 41 |
+
pe._rope_uninitialized = True
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def _collect_shared_weight_keys(module: torch.nn.Module) -> dict[str, str]:
|
| 45 |
+
# remove_duplicate=False is essential: by default named_parameters yields
|
| 46 |
+
# each shared tensor only once, which would hide exactly the duplicates we
|
| 47 |
+
# need to declare (and which state_dict still emits under every name).
|
| 48 |
+
names_by_storage: dict[int, list[str]] = collections.defaultdict(list)
|
| 49 |
+
for name, parameter in module.named_parameters(remove_duplicate=False):
|
| 50 |
+
names_by_storage[id(parameter)].append(name)
|
| 51 |
+
shared_keys: dict[str, str] = {}
|
| 52 |
+
for names in names_by_storage.values():
|
| 53 |
+
if len(names) > 1:
|
| 54 |
+
names = sorted(names)
|
| 55 |
+
# Prefer token_embeddings.weight as the canonical source
|
| 56 |
+
# (HF convention: output weights tied to input embeddings).
|
| 57 |
+
source = names[0]
|
| 58 |
+
for n in names:
|
| 59 |
+
if n.endswith("token_embeddings.weight"):
|
| 60 |
+
source = n
|
| 61 |
+
break
|
| 62 |
+
for target in names:
|
| 63 |
+
if target != source:
|
| 64 |
+
shared_keys[target] = source
|
| 65 |
+
return shared_keys
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
class _ExpandedTiedWeightsMixin:
|
| 69 |
+
def get_expanded_tied_weights_keys(self, all_submodels: bool = False) -> dict:
|
| 70 |
+
# HF's default returns {} when tie_word_embeddings=False, which would
|
| 71 |
+
# discard the mlm_head.linear_out <-> lm_head tie for NextLat models
|
| 72 |
+
# (tie_lm_head=False). Return the dynamically computed mapping directly
|
| 73 |
+
# so all shared weights are properly tied on load.
|
| 74 |
+
return dict(self._tied_weights_keys)
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
class DatModel(_ExpandedTiedWeightsMixin, PreTrainedModel):
|
| 78 |
+
config_class = DatConfig
|
| 79 |
+
base_model_prefix = ""
|
| 80 |
+
|
| 81 |
+
def __init__(self, config: DatConfig) -> None:
|
| 82 |
+
super().__init__(config)
|
| 83 |
+
dat_config = DatLMConfig(**{field: getattr(config, field) for field in DAT_LM_FIELDS})
|
| 84 |
+
self.model = DatDecoderLM(dat_config)
|
| 85 |
+
self._tied_weights_keys = _collect_shared_weight_keys(self)
|
| 86 |
+
self.post_init()
|
| 87 |
+
|
| 88 |
+
def post_init(self) -> None:
|
| 89 |
+
super().post_init()
|
| 90 |
+
# Work around accelerate/transformers leaving persistent=False buffers uninitialized
|
| 91 |
+
_refresh_rope_buffers(self.model, self.config.rope_theta)
|
| 92 |
+
|
| 93 |
+
def get_input_embeddings(self) -> torch.nn.Module:
|
| 94 |
+
return self.model.token_embeddings
|
| 95 |
+
|
| 96 |
+
def set_input_embeddings(self, value: torch.nn.Module) -> None:
|
| 97 |
+
self.model.token_embeddings = value
|
| 98 |
+
|
| 99 |
+
def forward(
|
| 100 |
+
self,
|
| 101 |
+
input_ids: torch.Tensor,
|
| 102 |
+
attention_mask: torch.Tensor | None = None,
|
| 103 |
+
**kwargs,
|
| 104 |
+
) -> BaseModelOutput:
|
| 105 |
+
_, hidden_states = self.model.encode_for_objective(input_ids, attention_mask=attention_mask)
|
| 106 |
+
return BaseModelOutput(last_hidden_state=hidden_states)
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
class DatForCausalLM(_ExpandedTiedWeightsMixin, PreTrainedModel):
|
| 110 |
+
config_class = DatConfig
|
| 111 |
+
base_model_prefix = "model"
|
| 112 |
+
_tied_weights_keys = {"model.lm_head.weight": "model.token_embeddings.weight"}
|
| 113 |
+
|
| 114 |
+
def __init__(self, config: DatConfig) -> None:
|
| 115 |
+
super().__init__(config)
|
| 116 |
+
dat_config = DatLMConfig(**{field: getattr(config, field) for field in DAT_LM_FIELDS})
|
| 117 |
+
self.model = DatDecoderLM(dat_config)
|
| 118 |
+
self._tied_weights_keys = _collect_shared_weight_keys(self)
|
| 119 |
+
self.post_init()
|
| 120 |
+
|
| 121 |
+
def post_init(self) -> None:
|
| 122 |
+
super().post_init()
|
| 123 |
+
# Work around accelerate/transformers leaving persistent=False buffers uninitialized
|
| 124 |
+
_refresh_rope_buffers(self.model, self.config.rope_theta)
|
| 125 |
+
|
| 126 |
+
def get_input_embeddings(self) -> torch.nn.Module:
|
| 127 |
+
return self.model.token_embeddings
|
| 128 |
+
|
| 129 |
+
def set_input_embeddings(self, value: torch.nn.Module) -> None:
|
| 130 |
+
self.model.token_embeddings = value
|
| 131 |
+
|
| 132 |
+
def get_output_embeddings(self) -> torch.nn.Module:
|
| 133 |
+
return self.model.lm_head
|
| 134 |
+
|
| 135 |
+
def set_output_embeddings(self, new_embeddings: torch.nn.Module) -> None:
|
| 136 |
+
self.model.lm_head = new_embeddings
|
| 137 |
+
|
| 138 |
+
def forward(
|
| 139 |
+
self,
|
| 140 |
+
input_ids: torch.Tensor,
|
| 141 |
+
attention_mask: torch.Tensor | None = None,
|
| 142 |
+
labels: torch.Tensor | None = None,
|
| 143 |
+
**kwargs,
|
| 144 |
+
) -> CausalLMOutput:
|
| 145 |
+
logits, _ = self.model(input_ids, attention_mask=attention_mask)
|
| 146 |
+
|
| 147 |
+
loss = None
|
| 148 |
+
if labels is not None:
|
| 149 |
+
shift_logits = logits[:, :-1, :].contiguous()
|
| 150 |
+
shift_labels = labels[:, 1:].contiguous()
|
| 151 |
+
loss = torch.nn.functional.cross_entropy(
|
| 152 |
+
shift_logits.view(-1, shift_logits.size(-1)),
|
| 153 |
+
shift_labels.view(-1),
|
| 154 |
+
ignore_index=-100,
|
| 155 |
+
)
|
| 156 |
+
|
| 157 |
+
return CausalLMOutput(loss=loss, logits=logits)
|
| 158 |
+
|
| 159 |
+
|
| 160 |
+
class DatForMaskedLM(_ExpandedTiedWeightsMixin, PreTrainedModel):
|
| 161 |
+
config_class = DatConfig
|
| 162 |
+
base_model_prefix = "model"
|
| 163 |
+
|
| 164 |
+
def __init__(self, config: DatConfig) -> None:
|
| 165 |
+
super().__init__(config)
|
| 166 |
+
dat_config = DatLMConfig(**{field: getattr(config, field) for field in DAT_LM_FIELDS})
|
| 167 |
+
self.model = DatDecoderLM(dat_config)
|
| 168 |
+
self._tied_weights_keys = _collect_shared_weight_keys(self)
|
| 169 |
+
self.post_init()
|
| 170 |
+
|
| 171 |
+
def post_init(self) -> None:
|
| 172 |
+
super().post_init()
|
| 173 |
+
_refresh_rope_buffers(self.model, self.config.rope_theta)
|
| 174 |
+
|
| 175 |
+
def get_input_embeddings(self) -> torch.nn.Module:
|
| 176 |
+
return self.model.token_embeddings
|
| 177 |
+
|
| 178 |
+
def set_input_embeddings(self, value: torch.nn.Module) -> None:
|
| 179 |
+
self.model.token_embeddings = value
|
| 180 |
+
|
| 181 |
+
def get_output_embeddings(self) -> torch.nn.Module:
|
| 182 |
+
return self.model.mlm_head.linear_out
|
| 183 |
+
|
| 184 |
+
def set_output_embeddings(self, new_embeddings: torch.nn.Module) -> None:
|
| 185 |
+
self.model.mlm_head.linear_out = new_embeddings
|
| 186 |
+
# Keep lm_head in sync with mlm_head.linear_out so that
|
| 187 |
+
# resize_token_embeddings updates both tied heads.
|
| 188 |
+
self.model.lm_head.weight = new_embeddings.weight
|
| 189 |
+
|
| 190 |
+
def forward(
|
| 191 |
+
self,
|
| 192 |
+
input_ids: torch.Tensor,
|
| 193 |
+
attention_mask: torch.Tensor | None = None,
|
| 194 |
+
labels: torch.Tensor | None = None,
|
| 195 |
+
**kwargs,
|
| 196 |
+
) -> MaskedLMOutput | tuple:
|
| 197 |
+
return_dict = kwargs.pop("return_dict", None)
|
| 198 |
+
if return_dict is None:
|
| 199 |
+
return_dict = self.config.return_dict
|
| 200 |
+
if kwargs:
|
| 201 |
+
raise TypeError(f"Unexpected keyword argument(s): {list(kwargs)}")
|
| 202 |
+
logits = self.model.forward_mlm(input_ids, attention_mask=attention_mask)
|
| 203 |
+
|
| 204 |
+
loss = None
|
| 205 |
+
if labels is not None:
|
| 206 |
+
loss = CrossEntropyLoss(ignore_index=-100)(
|
| 207 |
+
logits.view(-1, logits.size(-1)),
|
| 208 |
+
labels.view(-1),
|
| 209 |
+
)
|
| 210 |
+
|
| 211 |
+
if not return_dict:
|
| 212 |
+
output = (logits,)
|
| 213 |
+
return ((loss,) + output) if loss is not None else output
|
| 214 |
+
|
| 215 |
+
return MaskedLMOutput(loss=loss, logits=logits)
|
| 216 |
+
|
| 217 |
+
class DatForSequenceClassification(_ExpandedTiedWeightsMixin, PreTrainedModel):
|
| 218 |
+
config_class = DatConfig
|
| 219 |
+
base_model_prefix = "model"
|
| 220 |
+
|
| 221 |
+
def __init__(self, config: DatConfig) -> None:
|
| 222 |
+
super().__init__(config)
|
| 223 |
+
self.num_labels = getattr(config, "num_labels", 2)
|
| 224 |
+
dat_config = DatLMConfig(**{field: getattr(config, field) for field in DAT_LM_FIELDS})
|
| 225 |
+
self.model = DatDecoderLM(dat_config)
|
| 226 |
+
self.score = torch.nn.Linear(config.hidden_dim, self.num_labels, bias=False)
|
| 227 |
+
self._tied_weights_keys = _collect_shared_weight_keys(self)
|
| 228 |
+
self.post_init()
|
| 229 |
+
|
| 230 |
+
def post_init(self) -> None:
|
| 231 |
+
super().post_init()
|
| 232 |
+
_refresh_rope_buffers(self.model, self.config.rope_theta)
|
| 233 |
+
|
| 234 |
+
def get_input_embeddings(self) -> torch.nn.Module:
|
| 235 |
+
return self.model.token_embeddings
|
| 236 |
+
|
| 237 |
+
def set_input_embeddings(self, value: torch.nn.Module) -> None:
|
| 238 |
+
self.model.token_embeddings = value
|
| 239 |
+
|
| 240 |
+
def forward(
|
| 241 |
+
self,
|
| 242 |
+
input_ids: torch.Tensor,
|
| 243 |
+
attention_mask: torch.Tensor | None = None,
|
| 244 |
+
labels: torch.Tensor | None = None,
|
| 245 |
+
**kwargs,
|
| 246 |
+
) -> SequenceClassifierOutput:
|
| 247 |
+
_, hidden_states = self.model.encode_for_objective(input_ids, attention_mask=attention_mask)
|
| 248 |
+
logits = self.score(hidden_states)
|
| 249 |
+
|
| 250 |
+
batch_size = input_ids.shape[0]
|
| 251 |
+
|
| 252 |
+
if self.config.pad_token_id is None:
|
| 253 |
+
last_non_pad_token = -1
|
| 254 |
+
else:
|
| 255 |
+
non_pad_mask = (input_ids != self.config.pad_token_id).to(logits.device, torch.int32)
|
| 256 |
+
if (non_pad_mask.sum(-1) == 0).any().item():
|
| 257 |
+
raise ValueError("Cannot pool sequence-classification logits for all-padding sequences")
|
| 258 |
+
token_indices = torch.arange(input_ids.shape[-1], device=logits.device, dtype=torch.int32)
|
| 259 |
+
last_non_pad_token = (token_indices * non_pad_mask).argmax(-1)
|
| 260 |
+
|
| 261 |
+
pooled_logits = logits[torch.arange(batch_size, device=logits.device), last_non_pad_token]
|
| 262 |
+
|
| 263 |
+
loss = None
|
| 264 |
+
if labels is not None:
|
| 265 |
+
if getattr(self.config, "problem_type", None) is None:
|
| 266 |
+
if self.num_labels == 1:
|
| 267 |
+
self.config.problem_type = "regression"
|
| 268 |
+
elif self.num_labels > 1 and (labels.dtype == torch.long or labels.dtype == torch.int):
|
| 269 |
+
self.config.problem_type = "single_label_classification"
|
| 270 |
+
else:
|
| 271 |
+
self.config.problem_type = "multi_label_classification"
|
| 272 |
+
|
| 273 |
+
if self.config.problem_type == "regression":
|
| 274 |
+
loss_fct = MSELoss()
|
| 275 |
+
if self.num_labels == 1:
|
| 276 |
+
loss = loss_fct(pooled_logits.squeeze(), labels.squeeze())
|
| 277 |
+
else:
|
| 278 |
+
loss = loss_fct(pooled_logits, labels)
|
| 279 |
+
elif self.config.problem_type == "single_label_classification":
|
| 280 |
+
loss_fct = CrossEntropyLoss()
|
| 281 |
+
loss = loss_fct(pooled_logits.view(-1, self.num_labels), labels.view(-1))
|
| 282 |
+
elif self.config.problem_type == "multi_label_classification":
|
| 283 |
+
loss_fct = BCEWithLogitsLoss()
|
| 284 |
+
loss = loss_fct(pooled_logits, labels)
|
| 285 |
+
|
| 286 |
+
return SequenceClassifierOutput(
|
| 287 |
+
loss=loss,
|
| 288 |
+
logits=pooled_logits,
|
| 289 |
+
hidden_states=hidden_states,
|
| 290 |
+
)
|
special_tokens_map.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"bos_token": "<s>", "eos_token": "</s>", "unk_token": "<unk>", "sep_token": "</s>", "pad_token": "<pad>", "cls_token": "<s>", "mask_token": "<mask>"}
|
tokenizer.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
tokenizer_config.json
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"tokenizer_class": "PreTrainedTokenizerFast",
|
| 3 |
+
"bos_token": "<s>",
|
| 4 |
+
"eos_token": "</s>",
|
| 5 |
+
"unk_token": "<unk>",
|
| 6 |
+
"sep_token": "</s>",
|
| 7 |
+
"pad_token": "<pad>",
|
| 8 |
+
"cls_token": "<s>",
|
| 9 |
+
"mask_token": "<mask>"
|
| 10 |
+
}
|
transformer_components.py
ADDED
|
@@ -0,0 +1,282 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Minimal transformer components for decoder LM training."""
|
| 2 |
+
|
| 3 |
+
import math
|
| 4 |
+
from dataclasses import dataclass
|
| 5 |
+
from typing import Optional, Tuple
|
| 6 |
+
|
| 7 |
+
import torch
|
| 8 |
+
import torch.nn as nn
|
| 9 |
+
from torch import Tensor
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
@dataclass(frozen=True)
|
| 13 |
+
class PositionalInfo:
|
| 14 |
+
pe_type: str
|
| 15 |
+
embeddings: Optional[Tensor] = None
|
| 16 |
+
rope_freqs: Optional[Tuple[Tensor, Tensor]] = None
|
| 17 |
+
rel_embeddings: Optional[Tensor] = None
|
| 18 |
+
apply_to_embeddings: bool = False
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
class RMSNorm(nn.Module):
|
| 22 |
+
def __init__(self, dim: int, eps: float = 1e-5):
|
| 23 |
+
super().__init__()
|
| 24 |
+
self.eps = eps
|
| 25 |
+
self.weight = nn.Parameter(torch.ones(dim))
|
| 26 |
+
|
| 27 |
+
def _norm(self, x: Tensor) -> Tensor:
|
| 28 |
+
return x * torch.rsqrt(x.pow(2).mean(-1, keepdim=True) + self.eps)
|
| 29 |
+
|
| 30 |
+
def forward(self, x: Tensor) -> Tensor:
|
| 31 |
+
if x.dtype in (torch.float16, torch.bfloat16):
|
| 32 |
+
output = self._norm(x.float()).type_as(x)
|
| 33 |
+
else:
|
| 34 |
+
output = self._norm(x)
|
| 35 |
+
return output * self.weight
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
class FeedForward(nn.Module):
|
| 39 |
+
def __init__(
|
| 40 |
+
self,
|
| 41 |
+
input_dim: int,
|
| 42 |
+
hidden_dim: int,
|
| 43 |
+
dropout: float = 0.0,
|
| 44 |
+
activation: str = "gelu",
|
| 45 |
+
use_bias: bool = True,
|
| 46 |
+
):
|
| 47 |
+
super().__init__()
|
| 48 |
+
self.activation = activation
|
| 49 |
+
self.use_swiglu = activation == "swiglu"
|
| 50 |
+
self.dropout = nn.Dropout(dropout)
|
| 51 |
+
if self.use_swiglu:
|
| 52 |
+
self.w_gate = nn.Linear(input_dim, hidden_dim, bias=use_bias)
|
| 53 |
+
self.w_up = nn.Linear(input_dim, hidden_dim, bias=use_bias)
|
| 54 |
+
self.w_down = nn.Linear(hidden_dim, input_dim, bias=use_bias)
|
| 55 |
+
self.activation_fn = nn.SiLU()
|
| 56 |
+
else:
|
| 57 |
+
self.linear1 = nn.Linear(input_dim, hidden_dim, bias=use_bias)
|
| 58 |
+
self.linear2 = nn.Linear(hidden_dim, input_dim, bias=use_bias)
|
| 59 |
+
if activation == "gelu":
|
| 60 |
+
self.activation_fn = nn.GELU()
|
| 61 |
+
elif activation == "relu":
|
| 62 |
+
self.activation_fn = nn.ReLU()
|
| 63 |
+
elif activation == "identity":
|
| 64 |
+
self.activation_fn = nn.Identity()
|
| 65 |
+
else:
|
| 66 |
+
raise ValueError(f"Unsupported ffn activation: {activation}")
|
| 67 |
+
self._init_weights()
|
| 68 |
+
|
| 69 |
+
def _init_weights(self) -> None:
|
| 70 |
+
for module in self.modules():
|
| 71 |
+
if isinstance(module, nn.Linear):
|
| 72 |
+
nn.init.xavier_uniform_(module.weight, gain=1.0)
|
| 73 |
+
if module.bias is not None:
|
| 74 |
+
nn.init.zeros_(module.bias)
|
| 75 |
+
|
| 76 |
+
def forward(self, x: Tensor) -> Tensor:
|
| 77 |
+
if self.use_swiglu:
|
| 78 |
+
gate = self.activation_fn(self.w_gate(x))
|
| 79 |
+
up = self.w_up(x)
|
| 80 |
+
x = gate * up
|
| 81 |
+
x = self.dropout(x)
|
| 82 |
+
return self.w_down(x)
|
| 83 |
+
|
| 84 |
+
x = self.linear1(x)
|
| 85 |
+
x = self.activation_fn(x)
|
| 86 |
+
x = self.dropout(x)
|
| 87 |
+
x = self.linear2(x)
|
| 88 |
+
return x
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
class PositionalEncoding(nn.Module):
|
| 92 |
+
def __init__(
|
| 93 |
+
self,
|
| 94 |
+
embedding_dim: int,
|
| 95 |
+
pe_type: str,
|
| 96 |
+
max_len: int,
|
| 97 |
+
theta: float = 10000.0,
|
| 98 |
+
max_rel_pos: Optional[int] = None,
|
| 99 |
+
init_range: float = 0.15,
|
| 100 |
+
):
|
| 101 |
+
super().__init__()
|
| 102 |
+
if embedding_dim <= 0:
|
| 103 |
+
raise ValueError(f"embedding_dim must be positive, got {embedding_dim}")
|
| 104 |
+
if max_len <= 0:
|
| 105 |
+
raise ValueError(f"max_len must be positive, got {max_len}")
|
| 106 |
+
if pe_type not in {"sinusoidal", "learned", "relative", "rope", "none"}:
|
| 107 |
+
raise ValueError(f"Unsupported pe_type: {pe_type}")
|
| 108 |
+
if theta <= 0.0:
|
| 109 |
+
raise ValueError(f"theta must be positive, got {theta}")
|
| 110 |
+
if init_range <= 0.0:
|
| 111 |
+
raise ValueError(f"init_range must be positive, got {init_range}")
|
| 112 |
+
|
| 113 |
+
self.embedding_dim = embedding_dim
|
| 114 |
+
self.pe_type = pe_type
|
| 115 |
+
self.max_len = max_len
|
| 116 |
+
|
| 117 |
+
if pe_type == "sinusoidal":
|
| 118 |
+
if embedding_dim % 2 != 0:
|
| 119 |
+
raise ValueError(f"Sinusoidal encoding requires even embedding_dim, got {embedding_dim}")
|
| 120 |
+
pe = self._precompute_sinusoidal_embeddings(
|
| 121 |
+
embedding_dim,
|
| 122 |
+
max_len,
|
| 123 |
+
device=torch.empty(0).device,
|
| 124 |
+
)
|
| 125 |
+
self.register_buffer("sinusoidal_embeddings", pe, persistent=False)
|
| 126 |
+
self._sinusoidal_uninitialized = True
|
| 127 |
+
elif pe_type == "learned":
|
| 128 |
+
self.position_embeddings = nn.Embedding(max_len, embedding_dim)
|
| 129 |
+
nn.init.uniform_(self.position_embeddings.weight, -init_range, init_range)
|
| 130 |
+
elif pe_type == "relative":
|
| 131 |
+
self.max_relative_position = max_rel_pos if max_rel_pos is not None else max_len // 2
|
| 132 |
+
if self.max_relative_position <= 0:
|
| 133 |
+
raise ValueError(
|
| 134 |
+
f"max_relative_position must be positive, got {self.max_relative_position}"
|
| 135 |
+
)
|
| 136 |
+
num_embeddings = 2 * self.max_relative_position + 1
|
| 137 |
+
self.rel_pos_embeddings_table = nn.Embedding(num_embeddings, embedding_dim)
|
| 138 |
+
nn.init.uniform_(self.rel_pos_embeddings_table.weight, -init_range, init_range)
|
| 139 |
+
elif pe_type == "rope":
|
| 140 |
+
if embedding_dim % 2 != 0:
|
| 141 |
+
raise ValueError(f"RoPE requires even embedding_dim, got {embedding_dim}")
|
| 142 |
+
self.theta = theta
|
| 143 |
+
cos, sin = self._precompute_rope_freqs(embedding_dim, max_len, theta)
|
| 144 |
+
self.register_buffer("rope_cos", cos, persistent=False)
|
| 145 |
+
self.register_buffer("rope_sin", sin, persistent=False)
|
| 146 |
+
self._rope_uninitialized = True
|
| 147 |
+
|
| 148 |
+
def get_positional_info(self, seq_len: int, device: torch.device) -> PositionalInfo:
|
| 149 |
+
if self.pe_type == "none":
|
| 150 |
+
return PositionalInfo(pe_type="none")
|
| 151 |
+
if self.pe_type == "sinusoidal":
|
| 152 |
+
if seq_len > self.sinusoidal_embeddings.size(0):
|
| 153 |
+
raise ValueError(
|
| 154 |
+
f"seq_len {seq_len} exceeds precomputed sinusoidal length {self.sinusoidal_embeddings.size(0)}"
|
| 155 |
+
)
|
| 156 |
+
if (
|
| 157 |
+
getattr(self, "_sinusoidal_uninitialized", False)
|
| 158 |
+
or self.sinusoidal_embeddings.device.type == "meta"
|
| 159 |
+
):
|
| 160 |
+
embeddings = self._precompute_sinusoidal_embeddings(
|
| 161 |
+
self.embedding_dim,
|
| 162 |
+
self.max_len,
|
| 163 |
+
device=device,
|
| 164 |
+
)
|
| 165 |
+
self.register_buffer("sinusoidal_embeddings", embeddings, persistent=False)
|
| 166 |
+
self._sinusoidal_uninitialized = False
|
| 167 |
+
return PositionalInfo(
|
| 168 |
+
pe_type="sinusoidal",
|
| 169 |
+
embeddings=self.sinusoidal_embeddings[:seq_len].to(device),
|
| 170 |
+
apply_to_embeddings=True,
|
| 171 |
+
)
|
| 172 |
+
if self.pe_type == "learned":
|
| 173 |
+
if seq_len > self.max_len:
|
| 174 |
+
raise ValueError(
|
| 175 |
+
f"seq_len {seq_len} exceeds maximum learned position length {self.max_len}"
|
| 176 |
+
)
|
| 177 |
+
positions = torch.arange(seq_len, dtype=torch.long, device=device)
|
| 178 |
+
return PositionalInfo(
|
| 179 |
+
pe_type="learned",
|
| 180 |
+
embeddings=self.position_embeddings(positions),
|
| 181 |
+
apply_to_embeddings=True,
|
| 182 |
+
)
|
| 183 |
+
if self.pe_type == "relative":
|
| 184 |
+
return PositionalInfo(
|
| 185 |
+
pe_type="relative",
|
| 186 |
+
rel_embeddings=self._generate_relative_embeddings(seq_len, device),
|
| 187 |
+
apply_to_embeddings=False,
|
| 188 |
+
)
|
| 189 |
+
if seq_len > self.rope_cos.size(0):
|
| 190 |
+
# Dynamically extend RoPE buffer (standard practice for extrapolation)
|
| 191 |
+
cos, sin = self._precompute_rope_freqs(self.embedding_dim, seq_len, self.theta)
|
| 192 |
+
self.register_buffer("rope_cos", cos, persistent=False)
|
| 193 |
+
self.register_buffer("rope_sin", sin, persistent=False)
|
| 194 |
+
self._rope_uninitialized = False
|
| 195 |
+
if getattr(self, "_rope_uninitialized", True) or torch.isnan(self.rope_cos[0, 0]):
|
| 196 |
+
cos, sin = self._precompute_rope_freqs(self.embedding_dim, self.max_len, self.theta)
|
| 197 |
+
self.rope_cos.copy_(cos)
|
| 198 |
+
self.rope_sin.copy_(sin)
|
| 199 |
+
self._rope_uninitialized = False
|
| 200 |
+
return PositionalInfo(
|
| 201 |
+
pe_type="rope",
|
| 202 |
+
rope_freqs=(
|
| 203 |
+
self.rope_cos[:seq_len].to(device),
|
| 204 |
+
self.rope_sin[:seq_len].to(device),
|
| 205 |
+
),
|
| 206 |
+
apply_to_embeddings=False,
|
| 207 |
+
)
|
| 208 |
+
|
| 209 |
+
@staticmethod
|
| 210 |
+
def _rope_inv_freq(dim: int, theta: float, device: torch.device | str) -> Tensor:
|
| 211 |
+
return 1.0 / (theta ** (torch.arange(0, dim, 2, device=device).float() / dim))
|
| 212 |
+
|
| 213 |
+
@staticmethod
|
| 214 |
+
def _precompute_sinusoidal_embeddings(
|
| 215 |
+
dim: int,
|
| 216 |
+
end: int,
|
| 217 |
+
device: torch.device | str,
|
| 218 |
+
) -> Tensor:
|
| 219 |
+
pe = torch.zeros(end, dim, device=device)
|
| 220 |
+
position = torch.arange(0, end, dtype=torch.float, device=device).unsqueeze(1)
|
| 221 |
+
div_term = PositionalEncoding._rope_inv_freq(dim, 10000.0, device=device)
|
| 222 |
+
pe[:, 0::2] = torch.sin(position * div_term)
|
| 223 |
+
pe[:, 1::2] = torch.cos(position * div_term)
|
| 224 |
+
return pe
|
| 225 |
+
|
| 226 |
+
@staticmethod
|
| 227 |
+
def _precompute_rope_freqs(dim: int, end: int, theta: float) -> Tuple[Tensor, Tensor]:
|
| 228 |
+
freqs = PositionalEncoding._rope_inv_freq(dim, theta, device="cpu")
|
| 229 |
+
positions = torch.arange(end, device="cpu")
|
| 230 |
+
freqs = torch.outer(positions, freqs).float()
|
| 231 |
+
return torch.cos(freqs), torch.sin(freqs)
|
| 232 |
+
|
| 233 |
+
def _generate_relative_embeddings(self, seq_len: int, device: torch.device) -> Tensor:
|
| 234 |
+
range_q = torch.arange(seq_len, device=device)
|
| 235 |
+
range_k = torch.arange(seq_len, device=device)
|
| 236 |
+
distance_mat = range_k[None, :] - range_q[:, None]
|
| 237 |
+
distance_mat_clipped = torch.clamp(
|
| 238 |
+
distance_mat,
|
| 239 |
+
-self.max_relative_position,
|
| 240 |
+
self.max_relative_position,
|
| 241 |
+
)
|
| 242 |
+
final_indices = distance_mat_clipped + self.max_relative_position
|
| 243 |
+
return self.rel_pos_embeddings_table(final_indices.long())
|
| 244 |
+
|
| 245 |
+
|
| 246 |
+
def reshape_for_broadcast(freqs_cis: Tensor, x: Tensor) -> Tensor:
|
| 247 |
+
ndim = x.ndim
|
| 248 |
+
if ndim < 2:
|
| 249 |
+
raise ValueError(f"Input tensor x must have at least 2 dimensions, got {ndim}")
|
| 250 |
+
if x.shape[-2] != freqs_cis.shape[0] or x.shape[-1] != freqs_cis.shape[-1]:
|
| 251 |
+
raise ValueError(
|
| 252 |
+
f"Shape mismatch for RoPE broadcasting: freqs_cis {freqs_cis.shape}, x {x.shape}"
|
| 253 |
+
)
|
| 254 |
+
shape = [1] * ndim
|
| 255 |
+
shape[-2] = x.shape[-2]
|
| 256 |
+
shape[-1] = x.shape[-1]
|
| 257 |
+
return freqs_cis.view(shape)
|
| 258 |
+
|
| 259 |
+
|
| 260 |
+
def apply_rotary_emb(xq: Tensor, xk: Tensor, freqs_cos: Tensor, freqs_sin: Tensor) -> Tuple[Tensor, Tensor]:
|
| 261 |
+
if xq.shape[-1] % 2 != 0:
|
| 262 |
+
raise ValueError(f"Query feature dimension must be even for RoPE, got {xq.shape[-1]}")
|
| 263 |
+
if xk.shape[-1] % 2 != 0:
|
| 264 |
+
raise ValueError(f"Key feature dimension must be even for RoPE, got {xk.shape[-1]}")
|
| 265 |
+
|
| 266 |
+
xq_r, xq_i = xq.float().reshape(xq.shape[:-1] + (-1, 2)).unbind(-1)
|
| 267 |
+
xk_r, xk_i = xk.float().reshape(xk.shape[:-1] + (-1, 2)).unbind(-1)
|
| 268 |
+
|
| 269 |
+
freqs_cos_q = reshape_for_broadcast(freqs_cos[: xq.shape[-2]], xq_r)
|
| 270 |
+
freqs_sin_q = reshape_for_broadcast(freqs_sin[: xq.shape[-2]], xq_r)
|
| 271 |
+
freqs_cos_k = reshape_for_broadcast(freqs_cos[: xk.shape[-2]], xk_r)
|
| 272 |
+
freqs_sin_k = reshape_for_broadcast(freqs_sin[: xk.shape[-2]], xk_r)
|
| 273 |
+
|
| 274 |
+
xq_out_r = xq_r * freqs_cos_q - xq_i * freqs_sin_q
|
| 275 |
+
xq_out_i = xq_r * freqs_sin_q + xq_i * freqs_cos_q
|
| 276 |
+
xk_out_r = xk_r * freqs_cos_k - xk_i * freqs_sin_k
|
| 277 |
+
xk_out_i = xk_r * freqs_sin_k + xk_i * freqs_cos_k
|
| 278 |
+
|
| 279 |
+
xq_out = torch.stack([xq_out_r, xq_out_i], dim=-1).flatten(-2)
|
| 280 |
+
xk_out = torch.stack([xk_out_r, xk_out_i], dim=-1).flatten(-2)
|
| 281 |
+
|
| 282 |
+
return xq_out.type_as(xq), xk_out.type_as(xk)
|
transformer_core.py
ADDED
|
@@ -0,0 +1,327 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Minimal self-attention core for decoder language models."""
|
| 2 |
+
|
| 3 |
+
import math
|
| 4 |
+
from typing import Optional, Tuple
|
| 5 |
+
|
| 6 |
+
import torch
|
| 7 |
+
import torch.nn as nn
|
| 8 |
+
|
| 9 |
+
from .transformer_components import PositionalInfo, apply_rotary_emb
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def _activate_scores(scores: torch.Tensor, activation: str) -> torch.Tensor:
|
| 13 |
+
if activation == "softmax":
|
| 14 |
+
return torch.softmax(scores, dim=-1)
|
| 15 |
+
if activation == "identity":
|
| 16 |
+
return scores
|
| 17 |
+
if activation == "relu":
|
| 18 |
+
return torch.relu(scores)
|
| 19 |
+
if activation == "tanh":
|
| 20 |
+
return torch.tanh(scores)
|
| 21 |
+
if activation == "sigmoid":
|
| 22 |
+
return torch.sigmoid(scores)
|
| 23 |
+
if activation == "gelu":
|
| 24 |
+
return torch.nn.functional.gelu(scores)
|
| 25 |
+
raise ValueError(f"Unsupported attention activation: {activation}")
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
class MultiHeadAttentionBase(nn.Module):
|
| 29 |
+
def __init__(
|
| 30 |
+
self,
|
| 31 |
+
query_dim: int,
|
| 32 |
+
output_dim: int,
|
| 33 |
+
key_dim: Optional[int] = None,
|
| 34 |
+
value_dim: Optional[int] = None,
|
| 35 |
+
n_heads: int = 4,
|
| 36 |
+
hidden_dim: int = 64,
|
| 37 |
+
dropout: float = 0.0,
|
| 38 |
+
total_n_heads: Optional[int] = None,
|
| 39 |
+
activation: str = "softmax",
|
| 40 |
+
use_bias_qkv: bool = False,
|
| 41 |
+
use_bias_out: bool = True,
|
| 42 |
+
):
|
| 43 |
+
super().__init__()
|
| 44 |
+
self.query_dim = query_dim
|
| 45 |
+
self.key_dim = query_dim if key_dim is None else key_dim
|
| 46 |
+
self.value_dim = self.key_dim if value_dim is None else value_dim
|
| 47 |
+
self.output_dim = output_dim
|
| 48 |
+
self.n_heads = n_heads
|
| 49 |
+
self.hidden_dim = hidden_dim
|
| 50 |
+
self.total_n_heads = n_heads if total_n_heads is None else total_n_heads
|
| 51 |
+
self.activation = activation
|
| 52 |
+
self.use_bias_qkv = use_bias_qkv
|
| 53 |
+
self.use_bias_out = use_bias_out
|
| 54 |
+
|
| 55 |
+
if self.total_n_heads <= 0:
|
| 56 |
+
raise ValueError(f"total_n_heads must be positive, got {self.total_n_heads}")
|
| 57 |
+
if hidden_dim % self.total_n_heads != 0:
|
| 58 |
+
raise ValueError(
|
| 59 |
+
f"hidden_dim ({hidden_dim}) must be divisible by total_n_heads ({self.total_n_heads})"
|
| 60 |
+
)
|
| 61 |
+
|
| 62 |
+
self.head_dim = hidden_dim // self.total_n_heads
|
| 63 |
+
self.scale = 1.0 / math.sqrt(self.head_dim)
|
| 64 |
+
projected_dim = n_heads * self.head_dim
|
| 65 |
+
self.q_proj = nn.Linear(self.query_dim, projected_dim, bias=use_bias_qkv)
|
| 66 |
+
self.k_proj = nn.Linear(self.key_dim, projected_dim, bias=use_bias_qkv)
|
| 67 |
+
self.v_proj = nn.Linear(self.value_dim, projected_dim, bias=use_bias_qkv)
|
| 68 |
+
self.o_proj = nn.Linear(projected_dim, output_dim, bias=use_bias_out)
|
| 69 |
+
self.dropout = nn.Dropout(dropout)
|
| 70 |
+
self.attn_dropout = nn.Dropout(dropout)
|
| 71 |
+
self.last_attn_weights = None
|
| 72 |
+
|
| 73 |
+
self._init_weights()
|
| 74 |
+
|
| 75 |
+
def _init_weights(self) -> None:
|
| 76 |
+
gain = 1.0 / math.sqrt(2.0)
|
| 77 |
+
for projection in (self.q_proj, self.k_proj, self.v_proj, self.o_proj):
|
| 78 |
+
nn.init.xavier_uniform_(projection.weight, gain=gain)
|
| 79 |
+
if projection.bias is not None:
|
| 80 |
+
nn.init.zeros_(projection.bias)
|
| 81 |
+
|
| 82 |
+
def _reshape_for_multihead(self, x: torch.Tensor, batch_size: int, seq_len: int) -> torch.Tensor:
|
| 83 |
+
x = x.view(batch_size, seq_len, self.n_heads, self.head_dim)
|
| 84 |
+
return x.transpose(1, 2)
|
| 85 |
+
|
| 86 |
+
def _process_mask(self, mask: Optional[torch.Tensor]) -> Optional[torch.Tensor]:
|
| 87 |
+
if mask is None:
|
| 88 |
+
return None
|
| 89 |
+
if mask.dim() == 2:
|
| 90 |
+
if mask.shape[0] != mask.shape[1]:
|
| 91 |
+
raise ValueError(
|
| 92 |
+
"2D decoder attention masks must be square [seq, seq]; "
|
| 93 |
+
f"got {tuple(mask.shape)}. Expand padding masks at the LM boundary."
|
| 94 |
+
)
|
| 95 |
+
return mask.bool().unsqueeze(0).unsqueeze(0)
|
| 96 |
+
if mask.dim() == 3:
|
| 97 |
+
return mask.bool().unsqueeze(1)
|
| 98 |
+
raise ValueError(f"Mask must be 2D or 3D, got {mask.dim()}D")
|
| 99 |
+
|
| 100 |
+
def _compute_attn_scores(
|
| 101 |
+
self,
|
| 102 |
+
q: torch.Tensor,
|
| 103 |
+
k: torch.Tensor,
|
| 104 |
+
pos_info: Optional[PositionalInfo] = None,
|
| 105 |
+
) -> torch.Tensor:
|
| 106 |
+
if pos_info is not None and pos_info.rope_freqs is not None:
|
| 107 |
+
freqs_cos, freqs_sin = pos_info.rope_freqs
|
| 108 |
+
q, k = apply_rotary_emb(q, k, freqs_cos, freqs_sin)
|
| 109 |
+
return torch.matmul(q, k.transpose(-2, -1)) * self.scale
|
| 110 |
+
|
| 111 |
+
def _apply_activation_and_mask(self, scores: torch.Tensor, mask: Optional[torch.Tensor]) -> torch.Tensor:
|
| 112 |
+
processed_mask = self._process_mask(mask)
|
| 113 |
+
if processed_mask is not None and self.activation in {"softmax", "sigmoid", "tanh"}:
|
| 114 |
+
scores.masked_fill_(~processed_mask, torch.finfo(scores.dtype).min)
|
| 115 |
+
weights = _activate_scores(scores, self.activation)
|
| 116 |
+
if processed_mask is not None:
|
| 117 |
+
weights = weights.masked_fill(~processed_mask, 0.0)
|
| 118 |
+
return weights
|
| 119 |
+
|
| 120 |
+
def _apply_mask(self, scores: torch.Tensor, mask: Optional[torch.Tensor]) -> torch.Tensor:
|
| 121 |
+
return self._apply_activation_and_mask(scores, mask)
|
| 122 |
+
|
| 123 |
+
def forward(
|
| 124 |
+
self,
|
| 125 |
+
query: torch.Tensor,
|
| 126 |
+
key: Optional[torch.Tensor] = None,
|
| 127 |
+
value: Optional[torch.Tensor] = None,
|
| 128 |
+
mask: Optional[torch.Tensor] = None,
|
| 129 |
+
pos_info: Optional[PositionalInfo] = None,
|
| 130 |
+
) -> Tuple[torch.Tensor, torch.Tensor]:
|
| 131 |
+
batch_size, seq_len, _ = query.shape
|
| 132 |
+
key = query if key is None else key
|
| 133 |
+
value = key if value is None else value
|
| 134 |
+
key_len = key.shape[1]
|
| 135 |
+
|
| 136 |
+
if key.shape[0] != batch_size or value.shape[0] != batch_size:
|
| 137 |
+
raise ValueError(
|
| 138 |
+
f"Batch size mismatch: query={batch_size}, key={key.shape[0]}, value={value.shape[0]}"
|
| 139 |
+
)
|
| 140 |
+
if key.shape[1] != value.shape[1]:
|
| 141 |
+
raise ValueError(
|
| 142 |
+
f"Key and value sequence length mismatch: {key.shape[1]} vs {value.shape[1]}"
|
| 143 |
+
)
|
| 144 |
+
|
| 145 |
+
q = self.q_proj(query)
|
| 146 |
+
k = self.k_proj(key)
|
| 147 |
+
v = self.v_proj(value)
|
| 148 |
+
|
| 149 |
+
q = self._reshape_for_multihead(q, batch_size, seq_len)
|
| 150 |
+
k = self._reshape_for_multihead(k, batch_size, key_len)
|
| 151 |
+
v = self._reshape_for_multihead(v, batch_size, key_len)
|
| 152 |
+
|
| 153 |
+
attn_scores = self._compute_attn_scores(q, k, pos_info)
|
| 154 |
+
attn_weights = self._apply_activation_and_mask(attn_scores, mask)
|
| 155 |
+
attn_weights = self.attn_dropout(attn_weights)
|
| 156 |
+
|
| 157 |
+
attn_output = torch.matmul(attn_weights, v)
|
| 158 |
+
attn_output = attn_output.transpose(1, 2).contiguous()
|
| 159 |
+
attn_output = attn_output.view(batch_size, seq_len, self.n_heads * self.head_dim)
|
| 160 |
+
attn_output = self.o_proj(attn_output)
|
| 161 |
+
attn_output = self.dropout(attn_output)
|
| 162 |
+
|
| 163 |
+
self.last_attn_weights = attn_weights.detach()
|
| 164 |
+
return attn_output, attn_weights
|
| 165 |
+
|
| 166 |
+
|
| 167 |
+
class SelfAttention(MultiHeadAttentionBase):
|
| 168 |
+
def __init__(
|
| 169 |
+
self,
|
| 170 |
+
input_dim: int,
|
| 171 |
+
n_heads: int = 4,
|
| 172 |
+
hidden_dim: int = 64,
|
| 173 |
+
dropout: float = 0.0,
|
| 174 |
+
supports_relative: bool = False,
|
| 175 |
+
total_n_heads: Optional[int] = None,
|
| 176 |
+
use_bias_qkv: bool = False,
|
| 177 |
+
use_bias_out: bool = True,
|
| 178 |
+
):
|
| 179 |
+
head_dim = hidden_dim // (n_heads if total_n_heads is None else total_n_heads)
|
| 180 |
+
super().__init__(
|
| 181 |
+
query_dim=input_dim,
|
| 182 |
+
output_dim=n_heads * head_dim,
|
| 183 |
+
key_dim=input_dim,
|
| 184 |
+
value_dim=input_dim,
|
| 185 |
+
n_heads=n_heads,
|
| 186 |
+
hidden_dim=hidden_dim,
|
| 187 |
+
dropout=dropout,
|
| 188 |
+
total_n_heads=total_n_heads,
|
| 189 |
+
activation="softmax",
|
| 190 |
+
use_bias_qkv=use_bias_qkv,
|
| 191 |
+
use_bias_out=use_bias_out,
|
| 192 |
+
)
|
| 193 |
+
self.supports_relative = supports_relative
|
| 194 |
+
if self.supports_relative:
|
| 195 |
+
self.rel_k_proj = nn.Linear(self.head_dim, self.head_dim, bias=False)
|
| 196 |
+
self.rel_v_proj = nn.Linear(self.head_dim, self.head_dim, bias=False)
|
| 197 |
+
nn.init.xavier_uniform_(self.rel_k_proj.weight)
|
| 198 |
+
nn.init.xavier_uniform_(self.rel_v_proj.weight)
|
| 199 |
+
|
| 200 |
+
def forward(
|
| 201 |
+
self,
|
| 202 |
+
x: torch.Tensor,
|
| 203 |
+
mask: Optional[torch.Tensor] = None,
|
| 204 |
+
pos_info: Optional[PositionalInfo] = None,
|
| 205 |
+
) -> Tuple[torch.Tensor, torch.Tensor]:
|
| 206 |
+
if self.supports_relative and pos_info is not None and pos_info.rel_embeddings is not None:
|
| 207 |
+
batch_size, seq_len, _ = x.shape
|
| 208 |
+
rel_embeddings = pos_info.rel_embeddings
|
| 209 |
+
if rel_embeddings.shape[:2] != (seq_len, seq_len):
|
| 210 |
+
raise ValueError(
|
| 211 |
+
f"Relative embeddings shape {rel_embeddings.shape} does not match sequence length {seq_len}"
|
| 212 |
+
)
|
| 213 |
+
if rel_embeddings.shape[2] != self.head_dim:
|
| 214 |
+
raise ValueError(
|
| 215 |
+
f"Relative embeddings dim {rel_embeddings.shape[2]} does not match head_dim {self.head_dim}"
|
| 216 |
+
)
|
| 217 |
+
|
| 218 |
+
rel_k = self.rel_k_proj(rel_embeddings)
|
| 219 |
+
rel_v = self.rel_v_proj(rel_embeddings)
|
| 220 |
+
|
| 221 |
+
q = self.q_proj(x)
|
| 222 |
+
k = self.k_proj(x)
|
| 223 |
+
v = self.v_proj(x)
|
| 224 |
+
|
| 225 |
+
q = self._reshape_for_multihead(q, batch_size, seq_len)
|
| 226 |
+
k = self._reshape_for_multihead(k, batch_size, seq_len)
|
| 227 |
+
v = self._reshape_for_multihead(v, batch_size, seq_len)
|
| 228 |
+
|
| 229 |
+
attn_scores = self._compute_attn_scores(q, k, pos_info)
|
| 230 |
+
rel_scores = torch.einsum("bhid,ijd->bhij", q, rel_k) * self.scale
|
| 231 |
+
attn_scores = attn_scores + rel_scores
|
| 232 |
+
|
| 233 |
+
attn_weights = self._apply_mask(attn_scores, mask)
|
| 234 |
+
attn_weights = self.attn_dropout(attn_weights)
|
| 235 |
+
|
| 236 |
+
attn_output = torch.matmul(attn_weights, v)
|
| 237 |
+
rel_output = torch.einsum("bhij,ijd->bhid", attn_weights, rel_v)
|
| 238 |
+
attn_output = attn_output + rel_output
|
| 239 |
+
|
| 240 |
+
attn_output = attn_output.transpose(1, 2).contiguous()
|
| 241 |
+
attn_output = attn_output.view(batch_size, seq_len, self.n_heads * self.head_dim)
|
| 242 |
+
attn_output = self.o_proj(attn_output)
|
| 243 |
+
attn_output = self.dropout(attn_output)
|
| 244 |
+
self.last_attn_weights = attn_weights.detach()
|
| 245 |
+
return attn_output, attn_weights
|
| 246 |
+
|
| 247 |
+
return super().forward(query=x, mask=mask, pos_info=pos_info)
|
| 248 |
+
|
| 249 |
+
|
| 250 |
+
class CrossAttention(MultiHeadAttentionBase):
|
| 251 |
+
def __init__(
|
| 252 |
+
self,
|
| 253 |
+
input_dim: int,
|
| 254 |
+
output_dim: int,
|
| 255 |
+
context_dim: Optional[int] = None,
|
| 256 |
+
n_heads: int = 8,
|
| 257 |
+
hidden_dim: int = 64,
|
| 258 |
+
dropout: float = 0.0,
|
| 259 |
+
supports_relative: bool = False,
|
| 260 |
+
use_bias_qkv: bool = False,
|
| 261 |
+
use_bias_out: bool = True,
|
| 262 |
+
):
|
| 263 |
+
resolved_context_dim = input_dim if context_dim is None else context_dim
|
| 264 |
+
super().__init__(
|
| 265 |
+
query_dim=input_dim,
|
| 266 |
+
key_dim=resolved_context_dim,
|
| 267 |
+
value_dim=resolved_context_dim,
|
| 268 |
+
output_dim=output_dim,
|
| 269 |
+
n_heads=n_heads,
|
| 270 |
+
hidden_dim=hidden_dim,
|
| 271 |
+
dropout=dropout,
|
| 272 |
+
activation="softmax",
|
| 273 |
+
use_bias_qkv=use_bias_qkv,
|
| 274 |
+
use_bias_out=use_bias_out,
|
| 275 |
+
)
|
| 276 |
+
self.supports_relative = supports_relative
|
| 277 |
+
if supports_relative:
|
| 278 |
+
self.rel_k_proj = nn.Linear(self.head_dim, self.head_dim, bias=False)
|
| 279 |
+
self.rel_v_proj = nn.Linear(self.head_dim, self.head_dim, bias=False)
|
| 280 |
+
nn.init.xavier_uniform_(self.rel_k_proj.weight)
|
| 281 |
+
nn.init.xavier_uniform_(self.rel_v_proj.weight)
|
| 282 |
+
|
| 283 |
+
def forward(
|
| 284 |
+
self,
|
| 285 |
+
inputs: torch.Tensor,
|
| 286 |
+
context: torch.Tensor,
|
| 287 |
+
mask: Optional[torch.Tensor] = None,
|
| 288 |
+
pos_info: Optional[PositionalInfo] = None,
|
| 289 |
+
) -> Tuple[torch.Tensor, torch.Tensor]:
|
| 290 |
+
if self.supports_relative and pos_info is not None and pos_info.rel_embeddings is not None:
|
| 291 |
+
batch_size, seq_len_q, _ = inputs.shape
|
| 292 |
+
_, seq_len_k, _ = context.shape
|
| 293 |
+
rel_embeddings = pos_info.rel_embeddings
|
| 294 |
+
if rel_embeddings.shape[:2] != (seq_len_q, seq_len_k):
|
| 295 |
+
raise ValueError(
|
| 296 |
+
f"Relative embeddings shape {rel_embeddings.shape} does not match "
|
| 297 |
+
f"sequence lengths {(seq_len_q, seq_len_k)}"
|
| 298 |
+
)
|
| 299 |
+
if rel_embeddings.shape[2] != self.head_dim:
|
| 300 |
+
raise ValueError(
|
| 301 |
+
f"Relative embeddings dim {rel_embeddings.shape[2]} does not match head_dim {self.head_dim}"
|
| 302 |
+
)
|
| 303 |
+
|
| 304 |
+
rel_k = self.rel_k_proj(rel_embeddings)
|
| 305 |
+
rel_v = self.rel_v_proj(rel_embeddings)
|
| 306 |
+
q = self._reshape_for_multihead(self.q_proj(inputs), batch_size, seq_len_q)
|
| 307 |
+
k = self._reshape_for_multihead(self.k_proj(context), batch_size, seq_len_k)
|
| 308 |
+
v = self._reshape_for_multihead(self.v_proj(context), batch_size, seq_len_k)
|
| 309 |
+
attn_scores = self._compute_attn_scores(q, k, pos_info)
|
| 310 |
+
attn_scores = attn_scores + torch.einsum("bhid,ijd->bhij", q, rel_k) * self.scale
|
| 311 |
+
attn_weights = self._apply_activation_and_mask(attn_scores, mask)
|
| 312 |
+
attn_weights = self.attn_dropout(attn_weights)
|
| 313 |
+
self.last_attn_weights = attn_weights.detach()
|
| 314 |
+
attn_output = torch.matmul(attn_weights, v)
|
| 315 |
+
attn_output = attn_output + torch.einsum("bhij,ijd->bhid", attn_weights, rel_v)
|
| 316 |
+
attn_output = attn_output.transpose(1, 2).contiguous().view(batch_size, seq_len_q, -1)
|
| 317 |
+
attn_output = self.o_proj(attn_output)
|
| 318 |
+
attn_output = self.dropout(attn_output)
|
| 319 |
+
return attn_output, attn_weights
|
| 320 |
+
|
| 321 |
+
return super().forward(
|
| 322 |
+
query=inputs,
|
| 323 |
+
key=context,
|
| 324 |
+
value=context,
|
| 325 |
+
mask=mask,
|
| 326 |
+
pos_info=pos_info,
|
| 327 |
+
)
|