Thank you very much for your reply. I am experimenting with a small dataset with 5376 training data and 3024 validation data. Here are the codes of the training parts:
#### Training process
def train(model, iterator, optimizer, criterion):
    
    epoch_loss = 0
    epoch_acc = 0
    
    model.train()
    
    for batch in iterator:
        
        optimizer.zero_grad()
        
        text, text_lengths = batch.t
        
        if len(text_lengths.numpy())==BATCH_SIZE:
        
            predictions = model(text, text_lengths).squeeze(1)
        
            loss = criterion(predictions, batch.l)
        
            acc = accuracy(predictions, batch.l)
        
            loss.backward()
        
            optimizer.step()
        
            epoch_loss += loss.item()
            epoch_acc += acc.item()
        
    return epoch_loss / len(iterator), epoch_acc / len(iterator)

#### Evaluation process
def evaluate(model, iterator, criterion):
    
    epoch_loss = 0
    epoch_acc = 0
    
    model.eval()
    
    with torch.no_grad():
    
        for batch in iterator:

            text, text_lengths = batch.t
            if len(text_lengths.numpy())==BATCH_SIZE:
            
                predictions = model(text, text_lengths).squeeze(1)
            
                loss = criterion(predictions, batch.l)
            
                acc = accuracy(predictions, batch.l)

                epoch_loss += loss.item()
                epoch_acc += acc.item()
        
    return epoch_loss / len(iterator), epoch_acc / len(iterator)

I added  if len(text_lengths.numpy())==BATCH_SIZE: because the validation data size is not divisible by the batch size, and this fixed the problem. Not sure whether this causes other problems.
Then the training loop is as below:
modelOutputName = 'LSTM-rnn.pt'

N_EPOCHS = 30

best_valid_loss = float('inf')

for epoch in range(N_EPOCHS):

    start_time = time.time()
    
    train_loss, train_acc = train(model_adv, train_iterator, optimizer, criterion)
    valid_loss, valid_acc = evaluate(model_adv, valid_iterator, criterion)
    
    end_time = time.time()

    epoch_mins, epoch_secs = epoch_time(start_time, end_time)
    
    if valid_loss < best_valid_loss:
        best_valid_loss = valid_loss
        torch.save(model_adv.state_dict(), modelOutputName)
    
    print(f'Epoch: {epoch+1:02} | Epoch Time: {epoch_mins}m {epoch_secs}s')
    print(f'\tTrain Loss: {train_loss:.3f} | Train Acc: {train_acc*100:.2f}%')
    print(f'\t Val. Loss: {valid_loss:.3f} |  Val. Acc: {valid_acc*100:.2f}%')

Thank you for all the suggestions. Could you explain more on how to overfit the model? I am not quite sure how this works in LSTM model. Do I just comment out dropout and use a smaller sample as you suggested? Thank you.