def causal_conv1d_update_ref(
            x, conv_state, weight, bias=None, activation=None, cache_seqlens=None
        ):
            """
            x: (batch, dim) or (batch, dim, seqlen)
            conv_state: (batch, dim, state_len), where state_len >= width - 1
            weight: (dim, width)
            bias: (dim,)
            cache_seqlens: (batch,), dtype int32.
                If not None, the conv_state is treated as a circular buffer.
                The conv_state will be updated by copying x to the
                conv_state starting at the index
                @cache_seqlens % state_len before performing the convolution.

            out: (batch, dim) or (batch, dim, seqlen)
            """
            if activation not in [None, "silu", "swish"]:
                raise NotImplementedError("activation must be None, silu, or swish")
            dtype_in = x.dtype
            unsqueeze = x.dim() == 2
            if unsqueeze:
                x = x.unsqueeze(-1)
            batch, dim, seqlen = x.shape
            width = weight.shape[1]
            state_len = conv_state.shape[-1]
            assert conv_state.shape == (batch, dim, state_len)
            assert weight.shape == (dim, width)
            if cache_seqlens is None:
                x_new = torch.cat([conv_state, x], dim=-1).to(
                    weight.dtype
                )  # (batch, dim, state_len + seqlen)
                conv_state.copy_(x_new[:, :, -state_len:])
            else:
                width_idx = torch.arange(
                    -(width - 1), 0, dtype=torch.long, device=x.device
                ).unsqueeze(0) + cache_seqlens.unsqueeze(1)
                width_idx = (
                    torch.remainder(width_idx, state_len)
                    .unsqueeze(1)
                    .expand(-1, dim, -1)
                )
                x_new = torch.cat([conv_state.gather(2, width_idx), x], dim=-1).to(
                    weight.dtype
                )
                copy_idx = torch.arange(
                    seqlen, dtype=torch.long, device=x.device
                ).unsqueeze(0) + cache_seqlens.unsqueeze(1)
                copy_idx = (
                    torch.remainder(copy_idx, state_len)
                    .unsqueeze(1)
                    .expand(-1, dim, -1)
                )
                conv_state.scatter_(2, copy_idx, x)
            out = F.conv1d(x_new, weight.unsqueeze(1), bias, padding=0, groups=dim)[
                :, :, -seqlen:
            ]
            if unsqueeze:
                out = out.squeeze(-1)
            return (out if activation is None else F.silu(out)).to(dtype=dtype_in)

            x: (batch, dim) or (batch, dim, seqlen)
            conv_state: (batch, dim, state_len), where state_len >= width - 1
            weight: (dim, width)
            bias: (dim,)
            cache_seqlens: (batch,), dtype int32.
                If not None, the conv_state is treated as a circular buffer.
                The conv_state will be updated by copying x to the
                conv_state starting at the index
                @cache_seqlens % state_len before performing the convolution.

            out: (batch, dim) or (batch, dim, seqlen)
            
tests/cpu/test_ipex_llm_module.py