Good day all,
I’m very sorry for constantly asking question. I’m actually a student and trying to learn how to use pytorch very well for my research in deep learning & signal processing. I have tried all possible ways to make the tensors’ dimensions compatible but couldn’t.
import os
import torch
from torch.autograd import Variable
import numpy as np

SNR_dB_min = 8
SNR_dB_max = 13
rho = 0.55
seed = 2
N = 60
K = 30
batch_size = 5000
Random_Channel = 'True'
dtype = torch.FloatTensor

def vec_multiplication(W, b):

    #A = torch.mm(W, b.unsqueeze(2))
    #C = A.unsqueeze(-1)
    A = np.dot(W, np.expand_dims(b, 2)).astype(np.float32)
    C = np.squeeze(A, axis = -1)
    C = torch.from_numpy(C)
    return C


class Modulator(object):
               
    def __init__(self, mod_type, K): 

        # Create constellation
        self.mod_type = mod_type
        self.K = K

        # Set modulation type
        if (mod_type not in ['BPSK', '4PAM']):
            raise(Exception('Modulator: Unknown modulation format'))
        if (self.mod_type == 'BPSK'):
            self.constellation = np.array([-1.0, 1.0])
            self.constellation = (torch.from_numpy(self.constellation)).type(dtype)
        elif (self.mod_type == '4PAM'):
            self.constellation = np.array([-3.0, -1.0, 1.0, 3.0])
            self.constellation = (torch.from_numpy(self.constellation)).type(dtype)
        
        self.constellation_size = self.constellation.shape[0]
       
        # Normalize constellation to unit power and convert to tensor
        self.constellation /= torch.sqrt(torch.mean(torch.abs(self.constellation)**2))
        #self.constellation = torch.Variable(self.constellation, requires_grad = False)
        return
    
    def random_indices(self, batch_size):
        '''Generate random constellation symbol indices'''
        indices = torch.FloatTensor(batch_size, self.K).uniform_(0, self.constellation_size).int()
  
        return indices
    
    def modulate(self, indices):
        '''Map indices to constellation symbols'''

        x = self.constellation[indices.long()]
        return x   
            
    def gen_toeplitz_channel(self, N, rho):    
        '''Generate a random channel matrix with Toeplitz Gramian H'H
        as done in the original paper
        '''
        HtH = np.zeros([self.K, self.K])
        for i in range(self.K):
            for j in range(self.K):
                HtH[i,j] = rho ** np.abs(i-j)            
        T = np.random.normal(size=[N,N])
        V, D, V = np.linalg.svd(HtH)
        U = np.linalg.svd(np.dot(T,np.transpose(T)))
        H = np.dot(np.dot(U[0][:,0:self.K],np.diagflat(np.sqrt(D))),V)  
        H = torch.from_numpy(H)      
        return H
    
    
    def channel(self, x, N, snr_db_min, snr_db_max, rho, random_channel=False):
        '''Simulate transmission over a random or static channel with SNRs draw uniformly
           from the range [snr_db_min, snr_db_max] 
        '''
        # Derive parameters from inputs
        batch_size = x.shape[0]
            
        # Channel matrices
        if random_channel:
            H = np.random.normal(0.0,1/np.sqrt(N), [batch_size, N, self.K]).astype(np.float32)
            H = torch.from_numpy(H)  
        else:
            H = (self.gen_toeplitz_channel(N, rho)).unsqueeze(0)
            H = H.repeat([batch_size, 1, 1])
            
        # Random noise
        snr_db = torch.FloatTensor(batch_size, 1).uniform_(SNR_dB_min, SNR_dB_max).type(dtype)
        w = torch.randn([batch_size, N]).type(dtype)
        w = torch.mul(w, torch.pow(10.0, -snr_db/20.0))

        # Channel output
        y = vec_multiplication(H, x) + w
        #y = torch.mm(torch.squeeze(H,-1), x) + w
    
        return y, H
    
    def demodulate(self, y):
        '''Find closest constellation symbols in Tensor'''
        shape = y.shape
        y = y.reshape([-1, 1])
        constellation = self.constellation.reshape([1, self.constellation_size])
        indices = torch.argmin((torch.abs(y - constellation)), 1).type(dtype)
        indices = indices.reshape(shape).long()
        return indices  

# Generate random transmit signals
mod = Modulator('BPSK', K)
indices = mod.random_indices(batch_size)
x = mod.modulate(indices)

# Send x through the channel
y, H = mod.channel(x, N, SNR_dB_min, SNR_dB_max, rho, random_channel='Random_Channel')


def zf(y, H):
    '''Zero-Forcing Detector
    Inputs:
        y: Tensor of shape [batch_size, N]
        H: Tensor of shape [batch_size, N, K]
        mod: Instance of the modulator class
    Outputs:
        indices: Tensor of shape [batch_size, K]
    '''
    # Projected channel output
    Hty = vec_multiplication(torch.transpose(H, 0, 2, 1), y)
    #Hty = torch.mm(torch.transpose(H, 0, 2, 1), y)

    # Gramian of transposed channel matrix
    HtH = torch.mm(H, H.t())

    # Inverse Gramian 
    HtHinv = torch.inverse(HtH)

    # ZF Detector
    x = vec_multiplication(HtHinv, Hty)
    #x = torch.mm(HtHinv, Hty)
    
    return x

When the code is run, it returns the following error:
Traceback (most recent call last):
  File "/Users/Abdullahi/Desktop/DETNET_INCOMPLETE/signal_modulation.py", line 120, in <module>
    y, H = mod.channel(x, N, SNR_dB_min, SNR_dB_max, rho, random_channel='Random_Channel')
  File "/Users/Abdullahi/Desktop/DETNET_INCOMPLETE/signal_modulation.py", line 100, in channel
    y = vec_multiplication(H, x) + w
RuntimeError: The size of tensor a (5000) must match the size of tensor b (60) at non-singleton dimension 2

Thank you very much.