Thanks for the explanation.
You idea is interesting, as you could process the input as images, i.e. the conv kernels would see some words (rows) and the coding of the words (columns).
As each word has a target, I think we still should reshape the data and target at the end.
Here is a small code example with some comments:
class MyModel(nn.Module):
    def __init__(self):
        super(MyModel, self).__init__()
        self.conv1 = nn.Conv2d(1, 3, 3, 1, 1)  # Use squared kernels
        self.pool1 = nn.MaxPool2d((1, 2))  # Pool only in width (word embeddings)
        self.conv2 = nn.Conv2d(3, 1, 3, 1, 1)
        self.pool2 = nn.MaxPool2d((1, 2))
        
        self.fc1 = nn.Linear(12, 9)
        
    def forward(self, x):
        x = F.relu(self.pool1(self.conv1(x)))
        x = F.relu(self.pool2(self.conv2(x)))
        x = x.permute(0, 2, 1, 3)  # change channels with height
        x = x.view(x.size(0)*x.size(1), -1)
        x = self.fc1(x)
        
        return x

batch_size = 12
x = torch.randn(batch_size, 9, 50)
target = torch.randint(0, 9, (batch_size, 9))  # Assuming your target has 9 values

model = MyModel()
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=1e-3)

for epoch in range(100):
    optimizer.zero_grad()
    output = model(x.unsqueeze(1))  # Unsqueeze to add channel dim
    loss = criterion(output, target.view(-1))  # Flatten target
    loss.backward()
    optimizer.step()
    
    print('Epoch {}, loss {}'.format(epoch, loss.item()))

What do you think about this approach? Does it make sense in your use case?