def METHOD_NAME(ctx, grad_output):
    # This is a pattern that is very convenient - at the top of backward
    # unpack saved_tensors and initialize all gradients w.r.t. inputs to
    # None. Thanks to the fact that additional trailing Nones are
    # ignored, the return statement is simple even when the function has
    # optional inputs.
    input, weight, bias = ctx.saved_tensors
    grad_input = grad_weight = grad_bias = None
    #print(f"backward shaped grad_output {grad_output.shape}, input {input.shape}, weight {weight.shape} and bias {bias.shape if bias is not None else None}")
    # These needs_input_grad checks are optional and there only to
    # improve efficiency. If you want to make your code simpler, you can
    # skip them. Returning gradients for inputs that don't require it is
    # not an error.
    if ctx.needs_input_grad[0]:
        #print(f"Computing grad input weight {weight.shape} grad_output {grad_output.shape}")
        grad_input = grad_output.matmul(weight)
        #print(f"Computed grad input {grad_input.shape}")
    if ctx.needs_input_grad[1]:
        #print("Computing grad weight")
        dim = grad_output.dim()
        if dim > 2:
            grad_weight = grad_output.reshape(-1,
                                              grad_output.shape[-1]).t().matmul(input.reshape(-1, input.shape[-1]))
        else:
            grad_weight = grad_output.t().matmul(input)
        #print(f"Computed grad weight grad_weight {grad_weight.shape}")
    if bias is not None and ctx.needs_input_grad[2]:
        #print("Computing grad bias")
        grad_bias = grad_output.sum(0)
        #print("Done computing grad bias")
        #print("needs bias")
    #print(f"backward shaped grad_input {grad_input.shape}, grad_weight {grad_weight.shape}, grad_bias {grad_bias.shape if grad_bias is not None else None}")
    return grad_input, grad_weight, grad_bias