I explained it above when answering your second question in my first post.
self.weight = torch.matmul(self.a, self.b) creates a whole other variable, and it is this variable that is involved in the computation graph, not a and b. But then if you want to know how a and b would be updated, you can calculate it yourself after the backward :
a.grad = dloss/da = dloss/dweigths * dweigths/da = weigths.grad * f(b), with f(b) = b or b^T if I am not mistaken
a = optimizer(a, learning_rate, a.grad), for example a = a - learning_rate*a.grad 

With self.weight = torch.matmul(self.a, self.b), it’s a bit like saying to the computation graph: hello dear graph, don’t worry about a and b when retropagating the gradient, they are now represented by self.weight (self). This fails because at this moment torch.matmul(self.a, self.b) is only a normal tensor, hence :
TypeError: cannot assign 'torch.FloatTensor' as parameter 'weight' (torch.nn.Parameter or None expected)

But you can do as below in the forward function so that the error disappears, but a and b won’t change anymore, but rather self.weight (I remind again and again that it is because it intervenes in the computation graph, and that a and b do not intervene anymore)
self.weight = torch.nn.Parameter(torch.matmul(self.a, self.b), requires_grad=True)
return self._conv_forward(input, self.weight, self.bias)

Do it as below (without self) and you will see that a and b will change :
weight = torch.matmul(self.a, self.b)
return self._conv_forward(input, weight, self.bias) # equivalent to return self._conv_forward(input, torch.matmul(self.a, self.b), self.bias)

Here it works because weight (without self) is just a bait, it’s just a temporary variable nothing more, the graph doesn’t even know it (if I’m not mistaken), but rather a and b. It doesn’t prevent a and b from intervening in the graph.
 This famous computation graph …