code: def forward(
      self,
      sentences,
      wordchars,
      wordchars_mask,
      tags,
      word_orig_idx,
      sentlens,
      wordlens,
      chars,
      charoffsets,
      charlens,
      char_orig_idx,
  ):
      device = next(self.parameters()).device
  
      def pack(x):
          return pack_padded_sequence(x, sentlens, batch_first=True)
  
      inputs = []
      batch_size = len(sentences)
  
      if self.args["word_emb_dim"] > 0:
          # extract static embeddings
          static_words, word_mask = self.extract_static_embeddings(
              self.args, sentences, self.vocab["word"]
          )
  
          word_mask = word_mask.to(device)
          static_words = static_words.to(device)
  
          word_static_emb = self.word_emb(static_words)
  
          if "delta" in self.vocab and self.delta_emb is not None:
              # masks should be the same
              delta_words, _ = self.extract_static_embeddings(
                  self.args, sentences, self.vocab["delta"]
              )
              delta_words = delta_words.to(device)
              # unclear whether to treat words in the main embedding
              # but not in delta as unknown
              # simple heuristic though - treating them as not
              # unknown keeps existing models the same when
              # separating models into the base WV and delta WV
              # also, note that at training time, words like this
              # did not show up in the training data, but are
              # not exactly UNK, so it makes sense
              delta_unk_mask = torch.eq(delta_words, UNK_ID)
              static_unk_mask = torch.not_equal(static_words, UNK_ID)
              unk_mask = delta_unk_mask * static_unk_mask
              delta_words[unk_mask] = PAD_ID
  
              delta_emb = self.delta_emb(delta_words)
              word_static_emb = word_static_emb + delta_emb
  
          word_emb = pack(word_static_emb)
          inputs += [word_emb]
  
      if self.bert_model is not None:
          device = next(self.parameters()).device
          processed_bert = extract_bert_embeddings(
              self.args["bert_model"],
              self.bert_tokenizer,
              self.bert_model,
              sentences,
              device,
              keep_endpoints=False,
              detach=not self.args.get("bert_finetune", False),
          )
          processed_bert = pad_sequence(
              processed_bert, batch_first=True
          )
          inputs += [pack(processed_bert)]
  
      def pad(x):
          return pad_packed_sequence(
              PackedSequence(x, word_emb.batch_sizes), batch_first=True
          )[0]
  
      if self.args["char"] and self.args["char_emb_dim"] > 0:
          if self.args.get("charlm", None):
              char_reps_forward = (
                  self.charmodel_forward.get_representation(
                      chars[0], charoffsets[0], charlens, char_orig_idx
                  )
              )
              char_reps_forward = PackedSequence(
                  char_reps_forward.data, char_reps_forward.batch_sizes
              )
              char_reps_backward = (
                  self.charmodel_backward.get_representation(
                      chars[1], charoffsets[1], charlens, char_orig_idx
                  )
              )
              char_reps_backward = PackedSequence(
                  char_reps_backward.data,
                  char_reps_backward.batch_sizes,
              )
              inputs += [char_reps_forward, char_reps_backward]
          else:
              char_reps = self.charmodel(
                  wordchars,
                  wordchars_mask,
                  word_orig_idx,
                  sentlens,
                  wordlens,
              )
              char_reps = PackedSequence(
                  char_reps.data, char_reps.batch_sizes
              )
              inputs += [char_reps]
  
      lstm_inputs = torch.cat([x.data for x in inputs], 1)
      if self.args["word_dropout"] > 0:
          lstm_inputs = self.worddrop(
              lstm_inputs, self.drop_replacement
          )
      lstm_inputs = self.drop(lstm_inputs)
      lstm_inputs = pad(lstm_inputs)
      lstm_inputs = self.lockeddrop(lstm_inputs)
      lstm_inputs = pack(lstm_inputs).data
  
      if self.input_transform:
          lstm_inputs = self.input_transform(lstm_inputs)
  
      lstm_inputs = PackedSequence(lstm_inputs, inputs[0].batch_sizes)
      lstm_outputs, _ = self.taggerlstm(
          lstm_inputs,
          sentlens,
          hx=(
              self.taggerlstm_h_init.expand(
                  2 * self.args["num_layers"],
                  batch_size,
                  self.args["hidden_dim"],
              ).contiguous(),
              self.taggerlstm_c_init.expand(
                  2 * self.args["num_layers"],
                  batch_size,
                  self.args["hidden_dim"],
              ).contiguous(),
          ),
      )
      lstm_outputs = lstm_outputs.data
  
      # prediction layer
      lstm_outputs = self.drop(lstm_outputs)
      lstm_outputs = pad(lstm_outputs)
      lstm_outputs = self.lockeddrop(lstm_outputs)
      lstm_outputs = pack(lstm_outputs).data
  
      loss = 0
      logits = []
      trans = []
      for idx, (tag_clf, crit) in enumerate(
          zip(self.tag_clfs, self.crits)
      ):
          if not self.args.get("connect_output_layers") or idx == 0:
              next_logits = pad(tag_clf(lstm_outputs)).contiguous()
          else:
              # here we pack the output of the previous round, then append it
              packed_logits = pack(next_logits).data
              input_logits = torch.cat(
                  [lstm_outputs, packed_logits], axis=1
              )
              next_logits = pad(tag_clf(input_logits)).contiguous()
          # the tag_mask lets us avoid backprop on a blank tag
          tag_mask = torch.eq(tags[:, :, idx], EMPTY_ID)
          next_loss, next_trans = crit(
              next_logits,
              torch.bitwise_or(tag_mask, word_mask),
              tags[:, :, idx],
          )
          loss = loss + next_loss
          logits.append(next_logits)
          trans.append(next_trans)
  
      return loss, logits, trans
  
imports: import os
  import logging
  import numpy
  import torch
  import torch.nn
  import torch.nn.functional
  from torch.nn.utils.rnn import pad_packed_sequence, pack_padded_sequence, pack_sequence, pad_sequence, PackedSequence
  from stanza.models.common.data import map_to_ids, get_long_tensor
  from stanza.models.common.exceptions import ForwardCharlmNotFoundError, BackwardCharlmNotFoundError
  from stanza.models.common.packed_lstm import PackedLSTM
  from stanza.models.common.dropout import WordDropout, LockedDropout
  from stanza.models.common.char_model import CharacterModel, CharacterLanguageModel
  from stanza.models.common.crf import CRFLoss
  from stanza.models.common.foundation_cache import load_bert
  from stanza.models.common.vocab import PAD_ID, UNK_ID, EMPTY_ID
  from stanza.models.common.bert_embedding import extract_bert_embeddings
label_desc: Use image node_id 4 for calling the NERTagger obj's underlying member method code with example usage: obj.forward(sentences, wordchars, wordchars_mask, tags, word_orig_idx, sentlens, wordlens, chars, charoffsets, charlens, char_orig_idx) and returns: loss,  logits,  trans, pack_padded_sequence, pad_packed_sequence