Taykhoom commited on
Commit
d6ace23
·
0 Parent(s):

Initial ERNIE-RNA Hugging Face port

Browse files
.gitattributes ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
README.md ADDED
@@ -0,0 +1,154 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ library_name: transformers
3
+ tags:
4
+ - RNA
5
+ - language-model
6
+ license: mit
7
+ ---
8
+
9
+ # ERNIE-RNA
10
+
11
+ ERNIE-RNA is an RNA-specific large language model that incorporates RNA base-pairing potential as a recurrent 2D structural bias into each attention layer, enabling the model to capture secondary structure information during pretraining.
12
+
13
+ ## Architecture
14
+
15
+ | Parameter | Value |
16
+ |---|---|
17
+ | Layers | 12 |
18
+ | Attention heads | 12 |
19
+ | Embedding dimension | 768 |
20
+ | FFN hidden dimension | 3072 (GELU) |
21
+ | Vocabulary size | 25 |
22
+ | Positional encoding | Sinusoidal (fairseq-style) |
23
+ | Normalization | LayerNorm (embedding and post-residual) |
24
+ | Architecture | Post-LN Transformer with recurrent 2D RNA pairing bias |
25
+ | Max sequence length | 1024 |
26
+
27
+ ### Vocabulary
28
+
29
+ | Token | ID | Notes |
30
+ |---|---|---|
31
+ | `<cls>` | 0 | Prepended to every sequence |
32
+ | `<pad>` | 1 | Padding token |
33
+ | `<eos>` | 2 | Appended to every sequence |
34
+ | `<unk>` | 3 | Unknown token |
35
+ | G | 4 | |
36
+ | A | 5 | |
37
+ | U | 6 | T is silently mapped to U during tokenization |
38
+ | C | 7 | |
39
+ | N | 8 | Ambiguous nucleotide |
40
+ | Y, R, S, K, W, M, D, H, V, B | 9-18 | IUPAC ambiguity codes |
41
+ | X | 19 | Unknown/ambiguous nucleotide |
42
+ | I | 20 | Inosine |
43
+ | madeupword0000-madeupword0002 | 21-23 | Placeholder tokens from the original fairseq vocabulary |
44
+ | `<mask>` | 24 | MLM mask token |
45
+
46
+ ### 2D RNA Pairing Bias
47
+
48
+ ERNIE-RNA computes a pairwise RNA base-pairing potential matrix from the input sequence at the start of each forward pass. This matrix (shape `[B, T, T, 1]`) is projected to `[B, H, T, T]` via a 2-layer MLP (1 -> 6 -> H, with GELU) and added to the attention logits in the first layer. The pre-softmax attention scores then become the updated 2D bias for the next layer, creating a recurrent structural information pathway across all 12 transformer layers.
49
+
50
+ Base-pairing scores: A-U = 2.0, G-C = 3.0, G-U wobble = 0.8.
51
+
52
+ ## Pretraining
53
+
54
+ - **Objective:** Masked language modeling (MLM) on RNA sequences
55
+ - **Data:** RNAcentral (non-redundant RNA sequences)
56
+ - **Source checkpoint:** `ERNIE-RNA_pretrain.pt`
57
+
58
+ ### Checkpoint selection
59
+
60
+ Single pretrained checkpoint from the original repository. Used as-is; no fine-tuned variants are included in this release.
61
+
62
+ ## Parity Verification
63
+
64
+ Hidden-state representations verified identical (max abs diff = 1.82e-06) to the original
65
+ implementation at all 13 representation levels (embedding + 12 transformer layers).
66
+ Verified on GPU with PyTorch 2.7 / CUDA 12.
67
+
68
+ Only `attn_implementation="eager"` is supported (see Implementation Notes).
69
+
70
+ ## Related Models
71
+
72
+ See the full [ERNIE-RNA collection](https://huggingface.co/collections/Taykhoom/ernie-rna-6a20c1a8ea56c00a74e2dd93).
73
+
74
+ | Model | Notes |
75
+ |---|---|
76
+ | **[Taykhoom/ERNIE-RNA](https://huggingface.co/Taykhoom/ERNIE-RNA)** | **Pretrained model (this model)** |
77
+ | [Taykhoom/ERNIE-RNA-SS](https://huggingface.co/Taykhoom/ERNIE-RNA-SS) | SS fine-tuned (RNA3DB), backbone only |
78
+ | [Taykhoom/ERNIE-RNA-MRL](https://huggingface.co/Taykhoom/ERNIE-RNA-MRL) | UTR MRL fine-tuned, backbone only |
79
+
80
+ ## Usage
81
+
82
+ ### Embedding generation
83
+
84
+ ```python
85
+ import torch
86
+ from transformers import AutoTokenizer, AutoModel
87
+
88
+ tokenizer = AutoTokenizer.from_pretrained("Taykhoom/ERNIE-RNA", trust_remote_code=True)
89
+ model = AutoModel.from_pretrained("Taykhoom/ERNIE-RNA", trust_remote_code=True)
90
+ model.eval()
91
+
92
+ sequences = ["AUGCAUGCAUGC", "GGGGCCCCGGGG"]
93
+ enc = tokenizer(sequences, return_tensors="pt", padding=True)
94
+
95
+ with torch.no_grad():
96
+ out = model(**enc)
97
+
98
+ cls_emb = out.last_hidden_state[:, 0, :] # (batch, 768) -- CLS token
99
+ token_emb = out.last_hidden_state # (batch, seq_len, 768)
100
+
101
+ # Intermediate layers
102
+ out_all = model(**enc, output_hidden_states=True)
103
+ layer6_emb = out_all.hidden_states[6] # (batch, seq_len, 768)
104
+ ```
105
+
106
+ ### MLM logits
107
+
108
+ ```python
109
+ import torch
110
+ from transformers import AutoTokenizer, AutoModelForMaskedLM
111
+
112
+ tokenizer = AutoTokenizer.from_pretrained("Taykhoom/ERNIE-RNA", trust_remote_code=True)
113
+ model = AutoModelForMaskedLM.from_pretrained("Taykhoom/ERNIE-RNA", trust_remote_code=True)
114
+ model.eval()
115
+
116
+ enc = tokenizer(["AUG<mask>AUG"], return_tensors="pt")
117
+ with torch.no_grad():
118
+ logits = model(**enc).logits # (1, seq_len, 25)
119
+ ```
120
+
121
+ ### Fine-tuning
122
+
123
+ Use the CLS token embedding (`last_hidden_state[:, 0, :]`) as input to a prediction head for sequence-level tasks. For token-level tasks, use `last_hidden_state` directly.
124
+
125
+ ## Implementation Notes
126
+
127
+ ERNIE-RNA's recurrent 2D bias is updated from the pre-softmax attention scores at every layer (the raw QK logits become the bias input for the next layer). Fused attention kernels (SDPA, FlashAttention) do not expose pre-softmax scores, so they cannot maintain this recurrent pathway. Only `attn_implementation="eager"` is supported; requesting `sdpa` or `flash_attention_2` raises a `ValueError`.
128
+
129
+ The `twod_proj` MLP is always run in float32 (matching the original) regardless of the model's compute dtype.
130
+
131
+ ## Citation
132
+
133
+ ```bibtex
134
+ @article{yin2025_ernierna,
135
+ title = {{ERNIE-RNA}: an {RNA} language model with structure-enhanced representations},
136
+ author = {Yin, Weijie and Zhang, Zhaoyu and Zhang, Shuo and He, Liang and Zhang, Ruiyang and Jiang, Rui and Liu, Gan and Wang, Jingyi and Zhang, Xuegong and Qin, Tao and Xie, Zhen},
137
+ journal = {Nature Communications},
138
+ volume = {16},
139
+ number = {1},
140
+ pages = {8407},
141
+ year = {2025},
142
+ doi = {10.1038/s41467-025-64972-0}
143
+ }
144
+ ```
145
+
146
+ ## Credits
147
+
148
+ Original model and code by Yin et al. Source: [GitHub](https://github.com/Bruce-ywj/ERNIE-RNA).
149
+ The HF conversion code was authored primarily by [Claude Code](https://claude.ai/code)
150
+ and reviewed manually by Taykhoom Dalal.
151
+
152
+ ## License
153
+
154
+ MIT, following the original repository.
config.json ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "activation_dropout": 0.0,
3
+ "activation_fn": "gelu",
4
+ "architectures": [
5
+ "ErnieRNAForMaskedLM"
6
+ ],
7
+ "attention_dropout": 0.1,
8
+ "attention_heads": 12,
9
+ "auto_map": {
10
+ "AutoConfig": "configuration_ernierna.ErnieRNAConfig",
11
+ "AutoModel": "modeling_ernierna.ErnieRNAModel",
12
+ "AutoModelForMaskedLM": "modeling_ernierna.ErnieRNAForMaskedLM"
13
+ },
14
+ "dropout": 0.1,
15
+ "embed_dim": 768,
16
+ "ffn_embed_dim": 3072,
17
+ "mask_idx": 24,
18
+ "max_positions": 1024,
19
+ "model_max_length": 1024,
20
+ "model_type": "ernie_rna",
21
+ "num_layers": 12,
22
+ "num_segments": 2,
23
+ "padding_idx": 1,
24
+ "transformers_version": "4.57.6",
25
+ "vocab_size": 25
26
+ }
configuration_ernierna.py ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from transformers import PretrainedConfig
2
+
3
+
4
+ class ErnieRNAConfig(PretrainedConfig):
5
+ model_type = "ernie_rna"
6
+
7
+ auto_map = {
8
+ "AutoConfig": "configuration_ernierna.ErnieRNAConfig",
9
+ "AutoModel": "modeling_ernierna.ErnieRNAModel",
10
+ "AutoModelForMaskedLM": "modeling_ernierna.ErnieRNAForMaskedLM",
11
+ }
12
+
13
+ def __init__(
14
+ self,
15
+ vocab_size=25,
16
+ num_layers=12,
17
+ embed_dim=768,
18
+ ffn_embed_dim=3072,
19
+ attention_heads=12,
20
+ dropout=0.1,
21
+ attention_dropout=0.1,
22
+ activation_dropout=0.0,
23
+ activation_fn="gelu",
24
+ max_positions=1024,
25
+ padding_idx=1,
26
+ mask_idx=24,
27
+ num_segments=2,
28
+ model_max_length=1024,
29
+ **kwargs,
30
+ ):
31
+ super().__init__(padding_idx=padding_idx, **kwargs)
32
+ self.auto_map = dict(type(self).auto_map)
33
+ self.vocab_size = vocab_size
34
+ self.num_layers = num_layers
35
+ self.embed_dim = embed_dim
36
+ self.ffn_embed_dim = ffn_embed_dim
37
+ self.attention_heads = attention_heads
38
+ self.dropout = dropout
39
+ self.attention_dropout = attention_dropout
40
+ self.activation_dropout = activation_dropout
41
+ self.activation_fn = activation_fn
42
+ self.max_positions = max_positions
43
+ self.mask_idx = mask_idx
44
+ self.num_segments = num_segments
45
+ self.model_max_length = model_max_length
modeling_ernierna.py ADDED
@@ -0,0 +1,322 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import math
2
+ import torch
3
+ import torch.nn as nn
4
+ import torch.nn.functional as F
5
+
6
+ from transformers import PreTrainedModel
7
+ from transformers.activations import ACT2FN
8
+ from transformers.modeling_outputs import BaseModelOutput, MaskedLMOutput
9
+
10
+ try:
11
+ from .configuration_ernierna import ErnieRNAConfig
12
+ except ImportError:
13
+ from configuration_ernierna import ErnieRNAConfig
14
+
15
+
16
+ class ErnieRNASinusoidalPositionalEmbedding(nn.Module):
17
+ def __init__(self, num_positions, embed_dim, padding_idx):
18
+ super().__init__()
19
+ self.embedding_dim = embed_dim
20
+ self.padding_idx = padding_idx
21
+ # Table size: need indices up to padding_idx + 1 + num_positions
22
+ table_size = padding_idx + 1 + num_positions
23
+ self.register_buffer("weights", self._get_embedding(table_size, embed_dim, padding_idx))
24
+
25
+ @staticmethod
26
+ def _get_embedding(num_embeddings, embedding_dim, padding_idx):
27
+ half_dim = embedding_dim // 2
28
+ emb = math.log(10000) / (half_dim - 1)
29
+ emb = torch.exp(torch.arange(half_dim, dtype=torch.float) * -emb)
30
+ emb = torch.arange(num_embeddings, dtype=torch.float).unsqueeze(1) * emb.unsqueeze(0)
31
+ emb = torch.cat([torch.sin(emb), torch.cos(emb)], dim=1).view(num_embeddings, -1)
32
+ if embedding_dim % 2 == 1:
33
+ emb = torch.cat([emb, torch.zeros(num_embeddings, 1)], dim=1)
34
+ if padding_idx is not None:
35
+ emb[padding_idx, :] = 0
36
+ return emb
37
+
38
+ def forward(self, input_ids):
39
+ mask = input_ids.ne(self.padding_idx).int()
40
+ positions = (torch.cumsum(mask, dim=1) * mask).long() + self.padding_idx
41
+ return self.weights.index_select(0, positions.view(-1)).view(
42
+ input_ids.shape[0], input_ids.shape[1], -1
43
+ ).detach()
44
+
45
+
46
+ class ErnieRNATwodProj(nn.Module):
47
+ def __init__(self, config):
48
+ super().__init__()
49
+ self.linear1 = nn.Linear(1, 6)
50
+ self.linear2 = nn.Linear(6, config.attention_heads)
51
+ self.activation_fn = ACT2FN[config.activation_fn]
52
+
53
+ def forward(self, x):
54
+ x = self.linear1(x)
55
+ x = self.activation_fn(x)
56
+ x = self.linear2(x)
57
+ return x
58
+
59
+
60
+ def _compute_pairing_bias(input_ids):
61
+ B, T = input_ids.shape
62
+ xi = input_ids.unsqueeze(2).expand(B, T, T)
63
+ xj = input_ids.unsqueeze(1).expand(B, T, T)
64
+
65
+ score = torch.zeros(B, T, T, dtype=torch.float32, device=input_ids.device)
66
+ score[(xi == 5) & (xj == 6)] = 2.0
67
+ score[(xi == 6) & (xj == 5)] = 2.0
68
+ score[(xi == 4) & (xj == 7)] = 3.0
69
+ score[(xi == 7) & (xj == 4)] = 3.0
70
+ score[(xi == 4) & (xj == 6)] = 0.8
71
+ score[(xi == 6) & (xj == 4)] = 0.8
72
+ return score.unsqueeze(-1) # [B, T, T, 1]
73
+
74
+
75
+ class ErnieRNAAttention(nn.Module):
76
+ def __init__(self, config):
77
+ super().__init__()
78
+ self.embed_dim = config.embed_dim
79
+ self.num_heads = config.attention_heads
80
+ self.head_dim = self.embed_dim // self.num_heads
81
+ assert self.head_dim * self.num_heads == self.embed_dim
82
+
83
+ self.q_proj = nn.Linear(self.embed_dim, self.embed_dim)
84
+ self.k_proj = nn.Linear(self.embed_dim, self.embed_dim)
85
+ self.v_proj = nn.Linear(self.embed_dim, self.embed_dim)
86
+ self.out_proj = nn.Linear(self.embed_dim, self.embed_dim)
87
+ self.dropout = nn.Dropout(config.attention_dropout)
88
+
89
+ def _to_bh_t_hd(self, tensor, tgt_len, bsz):
90
+ return tensor.contiguous().view(tgt_len, bsz * self.num_heads, self.head_dim).transpose(0, 1)
91
+
92
+ def forward(self, x, key_padding_mask=None, twod_bias=None, output_attentions=False):
93
+ tgt_len, bsz, _ = x.size()
94
+
95
+ q = self._to_bh_t_hd(self.q_proj(x), tgt_len, bsz)
96
+ k = self._to_bh_t_hd(self.k_proj(x), tgt_len, bsz)
97
+ v = self._to_bh_t_hd(self.v_proj(x), tgt_len, bsz)
98
+
99
+ scale = self.head_dim ** -0.5
100
+ q = q * scale
101
+
102
+ attn_weights = torch.bmm(q, k.transpose(-2, -1)) # [B*H, T, T]
103
+
104
+ if key_padding_mask is not None:
105
+ attn_weights = attn_weights.view(bsz, self.num_heads, tgt_len, tgt_len)
106
+ attn_weights = attn_weights.masked_fill(
107
+ key_padding_mask.unsqueeze(1).unsqueeze(2), float("-inf")
108
+ )
109
+ attn_weights = attn_weights.view(bsz * self.num_heads, tgt_len, tgt_len)
110
+
111
+ if twod_bias is not None:
112
+ attn_weights = attn_weights + twod_bias.reshape(bsz * self.num_heads, tgt_len, tgt_len)
113
+
114
+ # Pre-softmax attention becomes the 2D bias for the next layer
115
+ twod_bias_new = attn_weights.view(bsz, self.num_heads, tgt_len, tgt_len)
116
+
117
+ attn_probs = F.softmax(attn_weights, dim=-1)
118
+ attn_weights_out = None
119
+ if output_attentions:
120
+ attn_weights_out = attn_probs.view(bsz, self.num_heads, tgt_len, tgt_len)
121
+ attn_probs = self.dropout(attn_probs)
122
+
123
+ out = torch.bmm(attn_probs, v)
124
+ out = out.transpose(0, 1).contiguous().view(tgt_len, bsz, self.embed_dim)
125
+ out = self.out_proj(out)
126
+
127
+ return out, attn_weights_out, twod_bias_new
128
+
129
+
130
+ class ErnieRNALayer(nn.Module):
131
+ def __init__(self, config):
132
+ super().__init__()
133
+ self.self_attn = ErnieRNAAttention(config)
134
+ self.self_attn_layer_norm = nn.LayerNorm(config.embed_dim)
135
+ self.fc1 = nn.Linear(config.embed_dim, config.ffn_embed_dim)
136
+ self.fc2 = nn.Linear(config.ffn_embed_dim, config.embed_dim)
137
+ self.final_layer_norm = nn.LayerNorm(config.embed_dim)
138
+ self.dropout = nn.Dropout(config.dropout)
139
+ self.activation_dropout = nn.Dropout(config.activation_dropout)
140
+ self.activation_fn = ACT2FN[config.activation_fn]
141
+
142
+ def forward(self, x, key_padding_mask=None, twod_bias=None, output_attentions=False):
143
+ residual = x
144
+ x, attn_weights, twod_bias_new = self.self_attn(
145
+ x,
146
+ key_padding_mask=key_padding_mask,
147
+ twod_bias=twod_bias,
148
+ output_attentions=output_attentions,
149
+ )
150
+ x = self.dropout(x)
151
+ x = self.self_attn_layer_norm(residual + x)
152
+
153
+ residual = x
154
+ x = self.activation_fn(self.fc1(x))
155
+ x = self.activation_dropout(x)
156
+ x = self.fc2(x)
157
+ x = self.dropout(x)
158
+ x = self.final_layer_norm(residual + x)
159
+
160
+ return x, attn_weights, twod_bias_new
161
+
162
+
163
+ class ErnieRNAModel(PreTrainedModel):
164
+ config_class = ErnieRNAConfig
165
+ base_model_prefix = "model"
166
+ _supports_sdpa = False
167
+ _supports_flash_attn_2 = False
168
+
169
+ def __init__(self, config):
170
+ super().__init__(config)
171
+ self.padding_idx = config.padding_idx
172
+
173
+ self.embed_tokens = nn.Embedding(config.vocab_size, config.embed_dim, padding_idx=config.padding_idx)
174
+ self.embed_positions = ErnieRNASinusoidalPositionalEmbedding(
175
+ config.max_positions, config.embed_dim, config.padding_idx
176
+ )
177
+ self.segment_embeddings = nn.Embedding(config.num_segments, config.embed_dim)
178
+ self.emb_layer_norm = nn.LayerNorm(config.embed_dim)
179
+ self.dropout = nn.Dropout(config.dropout)
180
+ self.layers = nn.ModuleList([ErnieRNALayer(config) for _ in range(config.num_layers)])
181
+ self.twod_proj = ErnieRNATwodProj(config)
182
+
183
+ self.post_init()
184
+
185
+ def forward(
186
+ self,
187
+ input_ids=None,
188
+ attention_mask=None,
189
+ token_type_ids=None,
190
+ output_attentions=None,
191
+ output_hidden_states=None,
192
+ return_dict=None,
193
+ ):
194
+ output_attentions = output_attentions if output_attentions is not None else self.config.output_attentions
195
+ output_hidden_states = output_hidden_states if output_hidden_states is not None else self.config.output_hidden_states
196
+ return_dict = return_dict if return_dict is not None else self.config.use_return_dict
197
+
198
+ # HF: 1=attend, 0=pad -> True=padding
199
+ if attention_mask is not None:
200
+ padding_mask = attention_mask.eq(0)
201
+ else:
202
+ padding_mask = input_ids.eq(self.padding_idx)
203
+
204
+ # Zero out padding positions after masking (matches fairseq behavior)
205
+ x = self.embed_tokens(input_ids)
206
+ # Sinusoidal PE is a float32 buffer; cast to activation dtype for bfloat16 compat.
207
+ x = x + self.embed_positions(input_ids).to(x.dtype)
208
+ if token_type_ids is not None:
209
+ x = x + self.segment_embeddings(token_type_ids)
210
+ x = self.emb_layer_norm(x)
211
+ if padding_mask.any():
212
+ x = x * (~padding_mask).unsqueeze(-1).to(x.dtype)
213
+ x = self.dropout(x)
214
+
215
+ # Compute initial 2D bias from sequence (always float32 as in original)
216
+ pairing = _compute_pairing_bias(input_ids) # [B, T, T, 1]
217
+ twod_proj_f32 = self.twod_proj.float()
218
+ twod_bias = twod_proj_f32(pairing.float()) # [B, T, T, H]
219
+ twod_bias = twod_bias.permute(0, 3, 1, 2).contiguous().to(x.dtype) # [B, H, T, T]
220
+
221
+ # Transpose to [T, B, C] for attention
222
+ x = x.transpose(0, 1)
223
+
224
+ all_hidden_states = []
225
+ all_attentions = []
226
+
227
+ if output_hidden_states:
228
+ all_hidden_states.append(x.transpose(0, 1))
229
+
230
+ key_padding_mask = padding_mask if padding_mask.any() else None
231
+
232
+ for layer in self.layers:
233
+ x, attn_weights, twod_bias = layer(
234
+ x,
235
+ key_padding_mask=key_padding_mask,
236
+ twod_bias=twod_bias,
237
+ output_attentions=output_attentions,
238
+ )
239
+ if output_hidden_states:
240
+ all_hidden_states.append(x.transpose(0, 1))
241
+ if output_attentions:
242
+ all_attentions.append(attn_weights)
243
+
244
+ x = x.transpose(0, 1) # [B, T, C]
245
+
246
+ if not return_dict:
247
+ return tuple(v for v in [x, tuple(all_hidden_states) or None, tuple(all_attentions) or None] if v is not None)
248
+
249
+ return BaseModelOutput(
250
+ last_hidden_state=x,
251
+ hidden_states=tuple(all_hidden_states) if output_hidden_states else None,
252
+ attentions=tuple(all_attentions) if output_attentions else None,
253
+ )
254
+
255
+
256
+ class ErnieRNALMHead(nn.Module):
257
+ def __init__(self, config):
258
+ super().__init__()
259
+ self.dense = nn.Linear(config.embed_dim, config.embed_dim)
260
+ self.layer_norm = nn.LayerNorm(config.embed_dim)
261
+ self.activation_fn = ACT2FN[config.activation_fn]
262
+ self.decoder = nn.Linear(config.embed_dim, config.vocab_size)
263
+
264
+ def forward(self, x):
265
+ x = self.layer_norm(self.activation_fn(self.dense(x)))
266
+ x = self.decoder(x)
267
+ return x
268
+
269
+
270
+ class ErnieRNAForMaskedLM(PreTrainedModel):
271
+ config_class = ErnieRNAConfig
272
+ base_model_prefix = "model"
273
+ _supports_sdpa = False
274
+ _supports_flash_attn_2 = False
275
+
276
+ def __init__(self, config):
277
+ super().__init__(config)
278
+ self.model = ErnieRNAModel(config)
279
+ self.lm_head = ErnieRNALMHead(config)
280
+ self.post_init()
281
+
282
+ def forward(
283
+ self,
284
+ input_ids=None,
285
+ attention_mask=None,
286
+ token_type_ids=None,
287
+ labels=None,
288
+ output_attentions=None,
289
+ output_hidden_states=None,
290
+ return_dict=None,
291
+ ):
292
+ return_dict = return_dict if return_dict is not None else self.config.use_return_dict
293
+
294
+ out = self.model(
295
+ input_ids,
296
+ attention_mask=attention_mask,
297
+ token_type_ids=token_type_ids,
298
+ output_attentions=output_attentions,
299
+ output_hidden_states=output_hidden_states,
300
+ return_dict=return_dict,
301
+ )
302
+
303
+ logits = self.lm_head(out[0])
304
+
305
+ loss = None
306
+ if labels is not None:
307
+ loss = F.cross_entropy(
308
+ logits.view(-1, self.config.vocab_size),
309
+ labels.view(-1),
310
+ ignore_index=-100,
311
+ )
312
+
313
+ if not return_dict:
314
+ output = (logits,) + out[1:]
315
+ return ((loss,) + output) if loss is not None else output
316
+
317
+ return MaskedLMOutput(
318
+ loss=loss,
319
+ logits=logits,
320
+ hidden_states=out.hidden_states,
321
+ attentions=out.attentions,
322
+ )
pytorch_model.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:10de02fd6d0034cd2e6987abffb3abafad75ac0c7420697d0572c45c4fac1bfb
3
+ size 345970527
special_tokens_map.json ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ {
2
+ "cls_token": "<cls>",
3
+ "eos_token": "<eos>",
4
+ "mask_token": "<mask>",
5
+ "pad_token": "<pad>",
6
+ "unk_token": "<unk>"
7
+ }
tokenization_ernierna.py ADDED
@@ -0,0 +1,115 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import os
3
+ from transformers import PreTrainedTokenizer
4
+
5
+
6
+ _VOCAB = {
7
+ "<cls>": 0,
8
+ "<pad>": 1,
9
+ "<eos>": 2,
10
+ "<unk>": 3,
11
+ "G": 4,
12
+ "A": 5,
13
+ "U": 6,
14
+ "C": 7,
15
+ "N": 8,
16
+ "Y": 9,
17
+ "R": 10,
18
+ "S": 11,
19
+ "K": 12,
20
+ "W": 13,
21
+ "M": 14,
22
+ "D": 15,
23
+ "H": 16,
24
+ "V": 17,
25
+ "B": 18,
26
+ "X": 19,
27
+ "I": 20,
28
+ "madeupword0000": 21,
29
+ "madeupword0001": 22,
30
+ "madeupword0002": 23,
31
+ "<mask>": 24,
32
+ }
33
+
34
+
35
+ class ErnieRNATokenizer(PreTrainedTokenizer):
36
+ vocab_files_names = {"vocab_file": "vocab.json"}
37
+ model_input_names = ["input_ids", "attention_mask"]
38
+
39
+ def __init__(
40
+ self,
41
+ vocab_file=None,
42
+ cls_token="<cls>",
43
+ pad_token="<pad>",
44
+ eos_token="<eos>",
45
+ unk_token="<unk>",
46
+ mask_token="<mask>",
47
+ **kwargs,
48
+ ):
49
+ if vocab_file is not None and os.path.isfile(vocab_file):
50
+ with open(vocab_file) as f:
51
+ self._vocab = json.load(f)
52
+ else:
53
+ self._vocab = dict(_VOCAB)
54
+ self._ids_to_tokens = {v: k for k, v in self._vocab.items()}
55
+
56
+ super().__init__(
57
+ cls_token=cls_token,
58
+ pad_token=pad_token,
59
+ eos_token=eos_token,
60
+ unk_token=unk_token,
61
+ mask_token=mask_token,
62
+ **kwargs,
63
+ )
64
+
65
+ @property
66
+ def vocab_size(self):
67
+ return len(self._vocab)
68
+
69
+ def get_vocab(self):
70
+ return dict(self._vocab)
71
+
72
+ def _tokenize(self, text):
73
+ tokens = []
74
+ for ch in text.upper():
75
+ if ch == "T":
76
+ tokens.append("U")
77
+ elif ch in self._vocab:
78
+ tokens.append(ch)
79
+ else:
80
+ tokens.append("<unk>")
81
+ return tokens
82
+
83
+ def _convert_token_to_id(self, token):
84
+ return self._vocab.get(token, self._vocab["<unk>"])
85
+
86
+ def _convert_id_to_token(self, index):
87
+ return self._ids_to_tokens.get(index, "<unk>")
88
+
89
+ def save_vocabulary(self, save_directory, filename_prefix=None):
90
+ os.makedirs(save_directory, exist_ok=True)
91
+ fname = (filename_prefix + "-" if filename_prefix else "") + "vocab.json"
92
+ path = os.path.join(save_directory, fname)
93
+ with open(path, "w") as f:
94
+ json.dump(self._vocab, f, indent=2)
95
+ return (path,)
96
+
97
+ def build_inputs_with_special_tokens(self, token_ids_0, token_ids_1=None):
98
+ cls = [self.cls_token_id]
99
+ eos = [self.eos_token_id]
100
+ if token_ids_1 is None:
101
+ return cls + token_ids_0 + eos
102
+ return cls + token_ids_0 + eos + cls + token_ids_1 + eos
103
+
104
+ def get_special_tokens_mask(self, token_ids_0, token_ids_1=None, already_has_special_tokens=False):
105
+ if already_has_special_tokens:
106
+ return super().get_special_tokens_mask(token_ids_0, token_ids_1, already_has_special_tokens=True)
107
+ mask = [1] + [0] * len(token_ids_0) + [1]
108
+ if token_ids_1 is not None:
109
+ mask += [1] + [0] * len(token_ids_1) + [1]
110
+ return mask
111
+
112
+ def create_token_type_ids_from_sequences(self, token_ids_0, token_ids_1=None):
113
+ if token_ids_1 is None:
114
+ return [0] + token_ids_0 + [0]
115
+ return [0] + token_ids_0 + [0, 0] + token_ids_1 + [0]
tokenizer_config.json ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "added_tokens_decoder": {
3
+ "0": {
4
+ "content": "<cls>",
5
+ "lstrip": false,
6
+ "normalized": false,
7
+ "rstrip": false,
8
+ "single_word": false,
9
+ "special": true
10
+ },
11
+ "1": {
12
+ "content": "<pad>",
13
+ "lstrip": false,
14
+ "normalized": false,
15
+ "rstrip": false,
16
+ "single_word": false,
17
+ "special": true
18
+ },
19
+ "2": {
20
+ "content": "<eos>",
21
+ "lstrip": false,
22
+ "normalized": false,
23
+ "rstrip": false,
24
+ "single_word": false,
25
+ "special": true
26
+ },
27
+ "3": {
28
+ "content": "<unk>",
29
+ "lstrip": false,
30
+ "normalized": false,
31
+ "rstrip": false,
32
+ "single_word": false,
33
+ "special": true
34
+ },
35
+ "24": {
36
+ "content": "<mask>",
37
+ "lstrip": false,
38
+ "normalized": false,
39
+ "rstrip": false,
40
+ "single_word": false,
41
+ "special": true
42
+ }
43
+ },
44
+ "clean_up_tokenization_spaces": false,
45
+ "cls_token": "<cls>",
46
+ "eos_token": "<eos>",
47
+ "extra_special_tokens": {},
48
+ "mask_token": "<mask>",
49
+ "auto_map": {
50
+ "AutoTokenizer": ["tokenization_ernierna.ErnieRNATokenizer", null]
51
+ },
52
+ "model_max_length": 1024,
53
+ "pad_token": "<pad>",
54
+ "tokenizer_class": "ErnieRNATokenizer",
55
+ "unk_token": "<unk>"
56
+ }
vocab.json ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "<cls>": 0,
3
+ "<pad>": 1,
4
+ "<eos>": 2,
5
+ "<unk>": 3,
6
+ "G": 4,
7
+ "A": 5,
8
+ "U": 6,
9
+ "C": 7,
10
+ "N": 8,
11
+ "Y": 9,
12
+ "R": 10,
13
+ "S": 11,
14
+ "K": 12,
15
+ "W": 13,
16
+ "M": 14,
17
+ "D": 15,
18
+ "H": 16,
19
+ "V": 17,
20
+ "B": 18,
21
+ "X": 19,
22
+ "I": 20,
23
+ "madeupword0000": 21,
24
+ "madeupword0001": 22,
25
+ "madeupword0002": 23,
26
+ "<mask>": 24
27
+ }