def METHOD_NAME(attrs, inputs, arg_types):
    """Legalize dense op for HVX vectorization and vrmpy tensorization.
    Given a workload with a matrix X of shape (M, K) and a matrix Y of (N, K),
    we first pad the N dimension to be a multiple of the output vector length.
    And if the inputs are signed or unsigned int8 and the Y matrix can be packed into the
    NK32n4k layout, we convert both inputs to uint8 to apply the most efficient variant of vrmpy.
    """
    new_attrs = {k: attrs[k] for k in attrs.keys()}
    # Collect the input tensors.
    x_tensor, y_tensor = arg_types[0], arg_types[1]
    dtype = x_tensor.dtype
    # Collect the output tensor.
    output_tensor = arg_types[2]
    # Collect the input exprs.
    x, y = inputs
    N, _ = y_tensor.shape
    if dtype == "float16":
        vec_len = 64
    elif "int8" in dtype:
        vec_len = 32
    else:
        return None
    if N % vec_len != 0:
        N_padded = ((N + vec_len) // vec_len) * vec_len
        dn = N_padded - N
        y_ = relay.nn.pad(y, pad_width=((0, dn), (0, 0)))
        # If units is explicitly specified, it is used to compute the output shape.
        # We need to update units after padding to prevent a type error.
        if attrs["units"] is not None:
            new_attrs["units"] = N + dn
        arg_types = [
            arg_types[0],
            tvm.ir.tensor_type.TensorType([N + dn, arg_types[1].shape[1]], arg_types[1].dtype),
        ]
        vrmpy_out = vrmpy_legalize(x, y_, arg_types, relay.nn.dense, new_attrs)
        if vrmpy_out is None:
            out_ = relay.nn.dense(x, y_, **new_attrs)
        else:
            out_ = vrmpy_out
        out = relay.strided_slice(out_, begin=[0, 0], end=[x.value for x in output_tensor.shape])
        return out
    return vrmpy_legalize(inputs[0], inputs[1], arg_types, relay.nn.dense, attrs)