#!/usr/bin/env python3 """ Parametric HAWK for pretraining AND the full BabyLM eval pipeline. Heads (all share ONE parametric backbone, so a CausalLM checkpoint maps 1:1 onto every head's wte/layers/norm_f/params/film/switch tensors -- only the task head is newly initialised): - HawkForCausalLM : LM pretraining + BLiMP / MultiBLiMP (zero-shot LL) - HawkForSequenceClassification: SuperGLUE-style fine-tuning (pooled last non-pad token) - HawkForTokenClassification : token tasks (per-token logits, ignore_index=-100) parametric_mode: "film" : InputInferredParameters router (k bits) + BCE param_sup + diffuse FiLM. "switch" : 3 named single-site switches, gold p teacher-forced by language, router kept as a detached inference predictor; wh_ex_situ->RG-LRU decay (memory horizon), v2/null_subject ->token-anchored expectation surrogate; force_params counterfactual. All parametric code is gated on config.k_params>0; k_params=0 == pristine Hawk backbone. transformers 4.x/5.x tie fix: `_tied_weights_keys` is a LIST and lm_head is HARD-TIED to wte in __init__ (sharing the Parameter object), so v5's meta-device / separate-storage tie resolution can't desync, and save_pretrained dedups lm_head.weight cleanly. """ from typing import Optional import torch, torch.nn as nn, torch.nn.functional as F import transformers from transformers import PreTrainedModel, PretrainedConfig from transformers.generation import GenerationMixin from transformers.modeling_outputs import ( BaseModelOutput, CausalLMOutputWithPast, SequenceClassifierOutput, TokenClassifierOutput) # transformers 4.x expects _tied_weights_keys as a LIST of target keys; 5.x expects a DICT # {target: source} (5.13 hard-crashes on the list form in post_init). Adapt at import time. _TF_MAJOR = int(transformers.__version__.split(".")[0]) _TIED_KEYS = ({"lm_head.weight": "wte.weight"} if _TF_MAJOR >= 5 else ["lm_head.weight"]) ROBERTS_TABLE = { # film mode "head_dir_clausal": (1.0, 0.5, 1.0), "v2": (0.0, 1.0, 0.0), "null_subject": (0.0, 0.0, 1.0), "tense_morph": (1.0, 1.0, 0.0), "sv_agreement": (1.0, 1.0, 0.0), "filler_gap": (1.0, 1.0, 0.0), "head_dir_dp": (0.5, 0.5, 0.0), "def_article": (1.0, 1.0, 0.0), "number_morph": (1.0, 1.0, 0.0), "classifier": (0.0, 0.0, 1.0), "binding_domain": (0.0, 0.0, 1.0), "rc_position": (1.0, 1.0, 0.0)} DEFAULT_SUPERVISE = ["v2", "filler_gap", "null_subject"] # rank-3 over eng/nld/zho SWITCH_BITS = {"v2": 0, "null_subject": 1, "wh_ex_situ": 2} SWITCH_TABLE = {"v2": (0.0, 1.0, 0.0), "null_subject": (0.0, 0.0, 1.0), "wh_ex_situ": (1.0, 1.0, 0.0)} SWITCH_SUPERVISE = ["v2", "null_subject", "wh_ex_situ"] def gumbel_sigmoid(logits, tau, hard=False, training=True): if training: u = torch.rand_like(logits).clamp_(1e-6, 1 - 1e-6) y = torch.sigmoid((logits + torch.log(u) - torch.log1p(-u)) / tau) else: y = torch.sigmoid(logits / tau) if hard: y = (y > 0.5).float() + y - y.detach() return y def diag_linear_scan(a, b): """Inclusive parallel scan of the diagonal affine recurrence h_t = a_t * h_{t-1} + b_t , h_0 = 0 (a, b: (B, T, W)) via Hillis-Steele in REAL space: ceil(log2 T) vectorised passes instead of a T-step Python loop. Exact (matches the loop up to float rounding, and with LOWER accumulated error since depth is log T not T), stable because a in (0,1) keeps the running products <=1 (no exp(-cumsum) overflow), and static-shape so torch.compile traces it with no data-dependent control flow (T is a python int -> the while unrolls).""" T = a.shape[1] A, H = a, b d = 1 while d < T: # unrolled at trace time (T static) one = A.new_ones(A.shape[0], d, A.shape[2]) zero = H.new_zeros(H.shape[0], d, H.shape[2]) A_prev = torch.cat([one, A[:, :-d]], dim=1) # a_{t-d}, identity 1 for t 0 else raw self._last_logits = logits return gumbel_sigmoid(logits, self.temperature, hard, self.training) # tensor tau: no recompile on anneal def ig_penalty(self): if self._last_logits is None: return self.default_logits.new_zeros(()) return ((self._last_logits - self.default_logits.unsqueeze(0)) ** 2).mean() class RMSNorm(nn.Module): def __init__(self, dim, eps=1e-6): super().__init__(); self.weight = nn.Parameter(torch.ones(dim)); self.eps = eps def forward(self, x): return self.weight * (x * torch.rsqrt(x.pow(2).mean(-1, keepdim=True) + self.eps)) class RGLRU(nn.Module): def __init__(self, width, c=8.0, use_parallel_scan=True): super().__init__() self.width = width; self.c = c; self.use_parallel_scan = use_parallel_scan self.input_gate = nn.Linear(width, width); self.recur_gate = nn.Linear(width, width) self.log_lambda = nn.Parameter(torch.empty(width).uniform_(2.197, 6.907)) def forward(self, x, recur_bias=None, decay_scale=None, slot=None): B, T, W = x.shape pre_r = self.recur_gate(x) if recur_bias is not None: pre_r = pre_r + recur_bias.unsqueeze(1) r = torch.sigmoid(pre_r) i = torch.sigmoid(self.input_gate(x)) log_a = -F.softplus(-self.log_lambda) log_a_t = self.c * r * log_a if decay_scale is not None: log_a_t = log_a_t * decay_scale.unsqueeze(1) if slot is not None: # wh-slot: (cmask (W,) 1 on protected channels, hold_eps, wh_t (B,T,1), reset_t (B,T,1)) cmask, hold_eps, wh_t, reset_t = slot log_a_t = log_a_t * (1.0 - cmask + cmask * hold_eps) # hold: a -> ~1 on the slot a_t = torch.exp(log_a_t) if slot is not None: a_t = a_t * (1.0 - cmask * reset_t) # boundary reset (PIC), gated upstream mult = torch.sqrt(torch.clamp(-torch.expm1(2.0 * log_a_t), min=1e-8)) gated_x = mult * (i * x) if slot is not None: # slot writes: only at wh tokens, UNNORMALISED (the sqrt(1-a^2) factor is ~0 on a # held channel and would crush the write -- the wh_gamma dead-at-init failure mode) gated_x = gated_x * (1.0 - cmask) + cmask * (i * x) * wh_t if self.use_parallel_scan: return diag_linear_scan(a_t, gated_x) # ceil(log2 T) parallel passes h = torch.zeros(B, W, device=x.device, dtype=x.dtype); outs = [] # exact sequential fallback for t in range(T): h = a_t[:, t] * h + gated_x[:, t]; outs.append(h) return torch.stack(outs, 1) class RecurrentBlock(nn.Module): def __init__(self, d_model, d_rnn, conv_kernel=4, rglru_c=8.0, use_parallel_scan=True): super().__init__() self.conv_kernel = conv_kernel self.in_gate = nn.Linear(d_model, d_rnn); self.in_recur = nn.Linear(d_model, d_rnn) self.conv = nn.Conv1d(d_rnn, d_rnn, conv_kernel, groups=d_rnn, padding=conv_kernel - 1) self.rglru = RGLRU(d_rnn, rglru_c, use_parallel_scan); self.out = nn.Linear(d_rnn, d_model) def forward(self, x, recur_bias=None, decay_scale=None, slot=None): gate = F.gelu(self.in_gate(x)) rec = self.in_recur(x).transpose(1, 2) rec = self.conv(rec)[..., : x.size(1)] rec = self.rglru(rec.transpose(1, 2), recur_bias, decay_scale, slot) return self.out(gate * rec) class MLPBlock(nn.Module): def __init__(self, d_model, expansion=3): super().__init__(); hidden = expansion * d_model self.gate = nn.Linear(d_model, hidden); self.up = nn.Linear(d_model, hidden) self.down = nn.Linear(hidden, d_model) def forward(self, x): return self.down(F.gelu(self.gate(x)) * self.up(x)) class HawkLayer(nn.Module): def __init__(self, d_model, d_rnn, conv_kernel, mlp_expansion, eps, rglru_c=8.0, use_parallel_scan=True): super().__init__() self.norm1 = RMSNorm(d_model, eps) self.recur = RecurrentBlock(d_model, d_rnn, conv_kernel, rglru_c, use_parallel_scan) self.norm2 = RMSNorm(d_model, eps); self.mlp = MLPBlock(d_model, mlp_expansion) def forward(self, x, recur_bias=None, decay_scale=None, slot=None): x = x + self.recur(self.norm1(x), recur_bias, decay_scale, slot) x = x + self.mlp(self.norm2(x)); return x class HawkConfig(PretrainedConfig): model_type = "hawk_rglru" def __init__(self, vocab_size=16384, n_layer=12, n_embd=768, rnn_width=None, conv_kernel=4, mlp_expansion=3, rmsnorm_eps=1e-6, rglru_c=8.0, max_position_embeddings=1024, tie_word_embeddings=True, bos_token_id=2, eos_token_id=3, pad_token_id=1, classifier_dropout=0.1, use_parallel_scan=True, k_params=0, parametric_mode="film", param_inject="embed", boundary_token_ids=None, wh_decay_gamma=0.5, wh_slot_channels=0, wh_token_ids=None, slot_hold_eps=0.02, lambda_param_sup=0.0, param_reg=1e-2, param_label_smooth=0.05, logit_cap=6.0, gumbel_temperature=1.0, **kwargs): self.vocab_size = vocab_size; self.n_layer = n_layer; self.n_embd = n_embd self.rnn_width = rnn_width; self.conv_kernel = conv_kernel; self.mlp_expansion = mlp_expansion self.rmsnorm_eps = rmsnorm_eps; self.rglru_c = rglru_c self.max_position_embeddings = max_position_embeddings; self.classifier_dropout = classifier_dropout self.use_parallel_scan = use_parallel_scan self.k_params = k_params; self.parametric_mode = parametric_mode; self.param_inject = param_inject self.boundary_token_ids = boundary_token_ids or []; self.wh_decay_gamma = wh_decay_gamma self.wh_slot_channels = wh_slot_channels; self.wh_token_ids = wh_token_ids or [] self.slot_hold_eps = slot_hold_eps self.lambda_param_sup = lambda_param_sup; self.param_reg = param_reg self.param_label_smooth = param_label_smooth; self.logit_cap = logit_cap self.gumbel_temperature = gumbel_temperature if parametric_mode == "switch" and k_params > 0: assert k_params == 3, "switch mode uses exactly 3 named bits (set k_params=3)" self.auto_map = { "AutoConfig": "modeling_hawk_param.HawkConfig", # AutoModel = parametric backbone only (last_hidden_state). Required by # finetune_token_classification.py, which wraps AutoModel in its own # PooledTokenClassifier rather than using AutoModelForTokenClassification. "AutoModel": "modeling_hawk_param.HawkModel", "AutoModelForCausalLM": "modeling_hawk_param.HawkForCausalLM", "AutoModelForSequenceClassification": "modeling_hawk_param.HawkForSequenceClassification", "AutoModelForTokenClassification": "modeling_hawk_param.HawkForTokenClassification"} super().__init__(tie_word_embeddings=tie_word_embeddings, bos_token_id=bos_token_id, eos_token_id=eos_token_id, pad_token_id=pad_token_id, **kwargs) @property def d_rnn(self): return self.rnn_width if self.rnn_width is not None else self.n_embd # Generic HF pipeline code reads config.hidden_size; HawkConfig calls it # n_embd. Alias it (read/write, so from_pretrained can set it if present). @property def hidden_size(self): return self.n_embd @hidden_size.setter def hidden_size(self, value): self.n_embd = value # --------------------------------------------------------------------------------------- # # Shared parametric backbone. Every head keeps wte/layers/norm_f (and the parametric # modules) as TOP-LEVEL attributes with identical names -> 1:1 state_dict mapping. # --------------------------------------------------------------------------------------- # class HawkPreTrainedModel(PreTrainedModel): config_class = HawkConfig supports_gradient_checkpointing = False def _init_backbone(self, config): H, d_rnn = config.n_embd, config.d_rnn self.wte = nn.Embedding(config.vocab_size, H) self.layers = nn.ModuleList([HawkLayer(H, d_rnn, config.conv_kernel, config.mlp_expansion, config.rmsnorm_eps, config.rglru_c, config.use_parallel_scan) for _ in range(config.n_layer)]) self.norm_f = RMSNorm(H, config.rmsnorm_eps) def _init_params(self, config): H, d_rnn = config.n_embd, config.d_rnn self.params = None; self.film_embed = None; self.film_recur = None self.wh_gamma = None; self.event_expect = None if config.k_params > 0: self.params = InputInferredParameters(H, config.k_params, config.gumbel_temperature, config.logit_cap) if config.parametric_mode == "switch": self.wh_gamma = nn.Parameter(torch.full((d_rnn,), float(config.wh_decay_gamma))) self.event_expect = nn.Parameter(torch.randn(2, 2, H) * 0.02) else: if config.param_inject in ("embed", "both"): self.film_embed = nn.Linear(config.k_params, H, bias=False); nn.init.normal_(self.film_embed.weight, std=0.02) if config.param_inject in ("rglru", "both"): self.film_recur = nn.Linear(config.k_params, d_rnn, bias=False); nn.init.normal_(self.film_recur.weight, std=0.02) self.register_buffer("param_targets", torch.zeros(0), persistent=False) self.register_buffer("param_sup_mask", torch.zeros(0), persistent=False) self._aux = {}; self._last_p = None # --- parametric API (shared) --- def get_input_embeddings(self): return self.wte def set_input_embeddings(self, new): self.wte = new def set_temperature(self, t): if getattr(self, "params", None) is not None: self.params.set_temperature(t); self.config.gumbel_temperature = float(t) def set_param_targets(self, langs, table=None, supervise=None, bit_assignment=None): if getattr(self, "params", None) is None: return {} if self.config.parametric_mode == "switch": table = table or SWITCH_TABLE; supervise = supervise or SWITCH_SUPERVISE bit_assignment = bit_assignment or SWITCH_BITS table = table or ROBERTS_TABLE; supervise = supervise or DEFAULT_SUPERVISE if bit_assignment is None: bit_assignment = {n: i for i, n in enumerate(supervise)} k = self.config.k_params; tgt = torch.zeros(len(langs), k); mask = torch.zeros(k) for name in supervise: b = bit_assignment[name]; mask[b] = 1.0 for li in range(len(langs)): tgt[li, b] = float(table[name][li]) dev = self.wte.weight.device self.param_targets = tgt.to(dev); self.param_sup_mask = mask.to(dev) return {lg: i for i, lg in enumerate(langs)} def aux_loss(self): if not self._aux: return self.wte.weight.new_zeros(()) c = self.config return (c.param_reg * self._aux.get("param", 0.0) + c.lambda_param_sup * self._aux.get("param_sup", 0.0)) def aux_components(self): return {k: (float(v.detach()) if torch.is_tensor(v) else float(v)) for k, v in self._aux.items()} def _backbone(self, input_ids, attention_mask=None, lang_ids=None, force_params=None): """embed -> (infer + inject typological params) -> layers -> norm_f. Sets self._aux.""" cfg = self.config; x = self.wte(input_ids); B = x.size(0) self._aux = {}; recur_bias = None; decay_scale = None sw = (cfg.parametric_mode == "switch") if getattr(self, "params", None) is not None: if attention_mask is None and cfg.pad_token_id is not None: attention_mask = (input_ids != cfg.pad_token_id).long() p_router = self.params(x, attention_mask) if sw: if lang_ids is not None and self.param_targets.numel() > 0: p = self.param_targets[lang_ids] else: p = (p_router > 0.5).to(x.dtype).detach() else: p = p_router if force_params is not None: p = p.clone() if isinstance(force_params, dict): for kb, v in force_params.items(): p[:, SWITCH_BITS[kb] if isinstance(kb, str) else int(kb)] = float(v) else: p = torch.as_tensor(force_params, dtype=p.dtype, device=p.device).expand_as(p).clone() self._last_p = p # grad-carrying: the wh-slot escape gate trains through it self._last_p_detached = p.detach() if sw: p_wh = p[:, 2:3] decay_scale = torch.exp(self.wh_gamma.unsqueeze(0) * (1.0 - 2.0 * p_wh)) E = self.event_expect # bit 0 (v2): expectation about clause POSITION 2 (finite verb second). # bit 1 (null_subject): expectation about clause POSITION 1 (overt argument # required vs droppable). The boundary token's step predicts position 1 -> # null_subject injects there (bmask); the position-1 token's step predicts # position 2 -> v2 injects there (amask). Injections also seed the RG-LRU # state, so their influence persists into the clause with the decay horizon. e_v2 = p[:, 0:1] * E[0, 1] + (1 - p[:, 0:1]) * E[0, 0] e_ns = p[:, 1:2] * E[1, 1] + (1 - p[:, 1:2]) * E[1, 0] if cfg.boundary_token_ids: bids = torch.as_tensor(cfg.boundary_token_ids, device=input_ids.device) bmask = torch.isin(input_ids, bids).to(x.dtype) # predicts clause pos 1 amask = F.pad(bmask, (1, 0))[:, :-1] # predicts clause pos 2 x = x + bmask.unsqueeze(-1) * e_ns.unsqueeze(1) + amask.unsqueeze(-1) * e_v2.unsqueeze(1) else: x = x + e_v2.unsqueeze(1) + e_ns.unsqueeze(1) else: if self.film_embed is not None: x = x + self.film_embed(p).unsqueeze(1) if self.film_recur is not None: recur_bias = self.film_recur(p) self._aux["param"] = self.params.ig_penalty() if (cfg.lambda_param_sup > 0 and lang_ids is not None and self.param_targets.numel() > 0): s = float(cfg.param_label_smooth) tgt = self.param_targets[lang_ids] * (1 - 2 * s) + s bce = F.binary_cross_entropy_with_logits(self.params._last_logits, tgt, reduction="none") msk = self.param_sup_mask.unsqueeze(0) self._aux["param_sup"] = (bce * msk).sum() / (msk.sum() * B).clamp(min=1.0) slot = None if cfg.wh_slot_channels > 0 and cfg.wh_token_ids: K = min(cfg.wh_slot_channels, cfg.d_rnn) cmask = x.new_zeros(cfg.d_rnn); cmask[-K:] = 1.0 # slot = last K RG-LRU channels wids = torch.as_tensor(cfg.wh_token_ids, device=input_ids.device) wh_t = torch.isin(input_ids, wids).to(x.dtype).unsqueeze(-1) # (B,T,1) writes reset_t = x.new_zeros(input_ids.shape + (1,)) if cfg.boundary_token_ids: bids = torch.as_tensor(cfg.boundary_token_ids, device=input_ids.device) bnd = torch.isin(input_ids, bids).to(x.dtype).unsqueeze(-1) # (B,T,1) # PIC with parameterised escape: filler_gap bit (film bit 1) holds the slot # across the boundary (eng/nld); its absence resets it there (zho). if self._last_p is not None and self._last_p.size(1) > 1: p_fg = self._last_p[:, 1:2].to(x.dtype) else: p_fg = x.new_zeros(x.size(0), 1) reset_t = bnd * (1.0 - p_fg)[:, None, :] slot = (cmask, float(cfg.slot_hold_eps), wh_t, reset_t) for layer in self.layers: x = layer(x, recur_bias, decay_scale, slot) return self.norm_f(x) class HawkModel(HawkPreTrainedModel): """Parametric backbone only: returns `last_hidden_state`, no LM/task head. finetune_token_classification.py builds its own head on top of `AutoModel.from_pretrained(...)` and reads `outputs.last_hidden_state`; HawkForCausalLM returns logits, so it cannot serve that role. Shares _init_backbone/_init_params/_backbone with every other head, so a CausalLM checkpoint maps 1:1 and nothing is newly initialised. The typological switchboard still runs (it lives in _backbone), so POS fine-tuning sees the same parametric representation the LM was trained with. """ def __init__(self, config: HawkConfig): super().__init__(config) self._init_backbone(config); self._init_params(config) self.post_init() def forward(self, input_ids, attention_mask=None, lang_ids=None, force_params=None, **kwargs): x = self._backbone(input_ids, attention_mask, lang_ids, force_params) return BaseModelOutput(last_hidden_state=x) class HawkForCausalLM(HawkPreTrainedModel, GenerationMixin): _tied_weights_keys = _TIED_KEYS # dict on tf>=5, list on 4.x @classmethod def from_pretrained(cls, *args, **kwargs): """transformers 5.x can leave lm_head on the meta device (tie declared but not executed: seen on 5.3.0 with only wte.weight in the file, and whenever BOTH weights are present the loader refuses to tie). Weights are hard-tied at training time, so unconditionally re-aliasing lm_head to wte after load is always correct.""" model = super().from_pretrained(*args, **kwargs) lm = getattr(model, "lm_head", None) if lm is not None: if lm.weight.device.type == "meta" or torch.equal(lm.weight, model.wte.weight): model.lm_head.weight = model.wte.weight elif lm.weight is not model.wte.weight: raise ValueError( "lm_head.weight differs from wte.weight but the checkpoint declares " "tie_word_embeddings=True — this checkpoint was trained UNTIED; " "re-aliasing would discard a trained output head.") return model def __init__(self, config: HawkConfig): super().__init__(config) self._init_backbone(config); self._init_params(config) self.lm_head = nn.Linear(config.n_embd, config.vocab_size, bias=False) self.lm_head.weight = self.wte.weight # HARD tie (same Parameter object) self.post_init() def get_output_embeddings(self): return self.lm_head def set_output_embeddings(self, new): self.lm_head = new def forward(self, input_ids, attention_mask=None, labels=None, lang_ids=None, force_params=None, **kwargs): x = self._backbone(input_ids, attention_mask, lang_ids, force_params) logits = self.lm_head(x) loss = None if labels is not None: sl = logits[:, :-1, :].contiguous(); st = labels[:, 1:].contiguous() loss = F.cross_entropy(sl.view(-1, sl.size(-1)), st.view(-1), ignore_index=-100) return CausalLMOutputWithPast(loss=loss, logits=logits) class HawkForSequenceClassification(HawkPreTrainedModel): """SuperGLUE-style fine-tuning. Pools the last non-pad hidden state (right padding). Backbone (incl. parametric modules) maps 1:1 from a CausalLM checkpoint; only `score` is newly initialised. Task loss only -- the pretraining aux terms are not folded in.""" def __init__(self, config: HawkConfig): super().__init__(config) self.num_labels = config.num_labels self._init_backbone(config); self._init_params(config) self.score = nn.Linear(config.n_embd, self.num_labels, bias=False) self.post_init() def forward(self, input_ids, attention_mask=None, labels=None, lang_ids=None, **kwargs): x = self._backbone(input_ids, attention_mask, lang_ids) logits = self.score(x) # (B,T,num_labels) B, T = input_ids.shape[:2] if attention_mask is not None: last = attention_mask.long().sum(-1) - 1 elif self.config.pad_token_id is not None: last = (input_ids != self.config.pad_token_id).int().sum(-1) - 1 else: last = torch.full((B,), T - 1, device=input_ids.device) last = last.clamp(min=0) pooled = logits[torch.arange(B, device=input_ids.device), last] # (B,num_labels) loss = None if labels is not None: if self.config.problem_type is None: if self.num_labels == 1: self.config.problem_type = "regression" elif self.num_labels > 1 and labels.dtype in (torch.long, torch.int): self.config.problem_type = "single_label_classification" else: self.config.problem_type = "multi_label_classification" if self.config.problem_type == "regression": lf = nn.MSELoss() loss = lf(pooled.squeeze(), labels.squeeze()) if self.num_labels == 1 else lf(pooled, labels) elif self.config.problem_type == "single_label_classification": loss = nn.CrossEntropyLoss()(pooled.view(-1, self.num_labels), labels.view(-1)) else: loss = nn.BCEWithLogitsLoss()(pooled, labels.float()) return SequenceClassifierOutput(loss=loss, logits=pooled) class HawkForTokenClassification(HawkPreTrainedModel): """Per-token classification (POS / morphology). Standard ignore_index=-100 loss.""" def __init__(self, config: HawkConfig): super().__init__(config) self.num_labels = config.num_labels self._init_backbone(config); self._init_params(config) drop = config.classifier_dropout if config.classifier_dropout is not None else 0.1 self.dropout = nn.Dropout(drop) self.classifier = nn.Linear(config.n_embd, self.num_labels) self.post_init() def forward(self, input_ids, attention_mask=None, labels=None, lang_ids=None, **kwargs): x = self._backbone(input_ids, attention_mask, lang_ids) logits = self.classifier(self.dropout(x)) # (B,T,num_labels) loss = None if labels is not None: loss = nn.CrossEntropyLoss()(logits.view(-1, self.num_labels), labels.view(-1)) return TokenClassifierOutput(loss=loss, logits=logits)