@staticmethod
def backward(ctx, grad_output):
    (bs, _, oy, ox) = grad_output.shape
    (tx, tw, x_shape) = ctx.saved_tensors
    (_, rcout, cin, H, W) = tw.shape
    (ys, xs) = ctx.stride
    (OY, OX) = x_shape[2:4]
    ggg = grad_output.reshape(bs, ctx.groups, rcout, oy, ox)
    gdw = np.zeros((ctx.groups, rcout, cin, H, W), dtype=tx.dtype)
    for g in range(ctx.groups):
        gdw[g] += np.tensordot(ggg[:, g], tx[:, g], ((0, 2, 3), (0, 2, 3)))
    gdx = np.zeros((bs, ctx.groups, cin, OY, OX), dtype=tx.dtype)
    for k in range((oy * ox)):
        (Y, X) = ((k // ox), (k % ox))
        (iY, iX) = ((Y * ys), (X * xs))
        for g in range(ctx.groups):
            tg = np.dot(ggg[:, g, :, Y, X].reshape(bs, (- 1)), tw[g].reshape(rcout, (- 1)))
            gdx[:, g, :, iY:(iY + H), iX:(iX + W)] += tg.reshape((bs, cin, H, W))
    return (gdx.reshape((bs, (ctx.groups * cin), OY, OX)), gdw.reshape(((ctx.groups * rcout), cin, H, W)))
@staticmethod
def backward(ctx, grad_output):
    (bs, _, oy, ox) = grad_output.shape
    (tx, tw, x_shape) = ctx.saved_tensors
    (_, rcout, cin, H, W) = tw.shape
    (ys, xs) = ctx.stride
    (OY, OX) = x_shape[2:4]
    ggg = grad_output.reshape(bs, ctx.groups, rcout, oy, ox)
    gdw = np.zeros((ctx.groups, rcout, cin, H, W), dtype=tx.dtype)
    for g in range(ctx.groups):
        gdw[g] += np.tensordot(ggg[:, g], tx[:, g], ((0, 2, 3), (0, 2, 3)))
    gdx = np.zeros((bs, ctx.groups, cin, OY, OX), dtype=tx.dtype)
    for k in range((oy * ox)):
        (Y, X) = ((k // ox), (k % ox))
        (iY, iX) = ((Y * ys), (X * xs))
        for g in range(ctx.groups):
            tg = np.dot(ggg[:, g, :, Y, X].reshape(bs, (- 1)), tw[g].reshape(rcout, (- 1)))
            gdx[:, g, :, iY:(iY + H), iX:(iX + W)] += tg.reshape((bs, cin, H, W))
    return (gdx.reshape((bs, (ctx.groups * cin), OY, OX)), gdw.reshape(((ctx.groups * rcout), cin, H, W)))