```python
import torch
import torch.nn as nn
from torch.utils.cpp_extension import load_inline

_fused_cuda_source = r"""
#include <torch/extension.h>
#include <ATen/cuda/CUDAContext.h>
#include <c10/cuda/CUDAGuard.h>
#include <cuda_runtime.h>
#include <cmath>

__global__ void groupnorm_stats_kernel(
    const float* __restrict__ x,
    float* __restrict__ mean,
    float* __restrict__ rstd,
    int N, int C, int S, int G, float eps) {
  const int gid = blockIdx.x;
  const int n = gid / G;
  const int g = gid - n * G;
  const int cpg = C / G;
  const int c0 = g * cpg;
  const double denom = static_cast<double>(static_cast<long long>(cpg) * S);

  double sum = 0.0;
  double sumsq = 0.0;
  for (int c = c0; c < c0 + cpg; ++c) {
    const float* ptr = x + (static_cast<long long>(n) * C + c) * S;
    for (int i = threadIdx.x; i < S; i += blockDim.x) {
      const float v = __ldg(ptr + i);
      sum += static_cast<double>(v);
      sumsq += static_cast<double>(v) * static_cast<double>(v);
    }
  }

  __shared__ double s_sum[256];
  __shared__ double s_sumsq[256];
  s_sum[threadIdx.x] = sum;
  s_sumsq[threadIdx.x] = sumsq;
  __syncthreads();

  for (int offset = blockDim.x >> 1; offset > 0; offset >>= 1) {
    if (threadIdx.x < offset) {
      s_sum[threadIdx.x] += s_sum[threadIdx.x + offset];
      s_sumsq[threadIdx.x] += s_sumsq[threadIdx.x + offset];
    }
    __syncthreads();
  }

  if (threadIdx.x == 0) {
    const double m = s_sum[0] / denom;
    double var = s_sumsq[0] / denom - m * m;
    if (var < 0.0) var = 0.0;
    mean[gid] = static_cast<float>(m);
    rstd[gid] = static_cast<float>(1.0 / ::sqrt(var + static_cast<double>(eps)));
  }
}

__global__ void fused_gn_tanh_hswish_residual_lse_kernel(
    const float* __restrict__ x,
    const float* __restrict__ mean,
    const float* __restrict__ rstd,
    const float* __restrict__ weight,
    const float* __restrict__ bias,
    float* __restrict__ out,
    int N, int C, int S, int G) {
  const long long p = static_cast<long long>(blockIdx.x) * blockDim.x + threadIdx.x;
  const long long total = static_cast<long long>(N) * S;
  if (p >= total) return;

  const int n = static_cast<int>(p / S);
  const int sp = static_cast<int>(p - static_cast<long long>(n) * S);
  const int cpg = C / G;
  const float* xn = x + static_cast<long long>(n) * C * S + sp;

  float m = -3.402823466e38f;
  float sum = 0.0f;

  for (int g = 0; g < G; ++g) {
    const float mu = __ldg(mean + n * G + g);
    const float rs = __ldg(rstd + n * G + g);
    const int c0 = g * cpg;
    for (int ci = 0; ci < cpg; ++ci) {
      const int c = c0 + ci;
      const float v = __ldg(xn + static_cast<long long>(c) * S);
      const float w = __ldg(weight + c);
      const float b = __ldg(bias + c);
      const float norm = (v - mu) * rs * w + b;
      const float t = tanhf(norm);
      const float hs = t * fminf(fmaxf(t + 3.0f, 0.0f), 6.0f) * (1.0f / 6.0f);
      const float r = v + hs;
      if (r > m) {
        sum = sum * expf(m - r) + 1.0f;
        m = r;
      } else {
        sum += expf(r - m);
      }
    }
  }

  out[p] = m + logf(sum);
}

torch::Tensor fused_gn_act_lse_cuda(
    torch::Tensor x,
    torch::Tensor weight,
    torch::Tensor bias,
    int64_t groups,
    double eps) {
  TORCH_CHECK(x.is_cuda(), "x must be CUDA");
  TORCH_CHECK(x.scalar_type() == at::kFloat, "x must be float32");
  TORCH_CHECK(x.dim() == 4, "x must be NCHW");
  TORCH_CHECK(groups > 0, "groups must be positive");

  auto x_c = x.contiguous();
  auto w_c = weight.contiguous();
  auto b_c = bias.contiguous();

  const int64_t N = x_c.size(0);
  const int64_t C = x_c.size(1);
  const int64_t H = x_c.size(2);
  const int64_t W = x_c.size(3);
  TORCH_CHECK(C % groups == 0, "channels must be divisible by groups");
  const int S = static_cast<int>(H * W);

  auto out = torch::empty({N, 1, H, W}, x_c.options());
  if (x_c.numel() == 0) {
    return out;
  }

  auto mean = torch::empty({N, groups}, x_c.options());
  auto rstd = torch::empty({N, groups}, x_c.options());

  const c10::cuda::CUDAGuard guard(x_c.device());
  auto stream = at::cuda::getCurrentCUDAStream().stream();

  const int threads = 256;
  const int stat_blocks = static_cast<int>(N * groups);
  groupnorm_stats_kernel<<<stat_blocks, threads, 0, stream>>>(
      x_c.data_ptr<float>(),
      mean.data_ptr<float>(),
      rstd.data_ptr<float>(),
      static_cast<int>(N), static_cast<int>(C), S, static_cast<int>(groups),
      static_cast<float>(eps));

  const long long total = static_cast<long long>(N) * S;
  const int blocks = static_cast<int>((total + threads - 1) / threads);
  fused_gn_tanh_hswish_residual_lse_kernel<<<blocks, threads, 0, stream>>>(
      x_c.data_ptr<float>(),
      mean.data_ptr<float>(),
      rstd.data_ptr<float>(),
      w_c.data_ptr<float>(),
      b_c.data_ptr<float>(),
      out.data_ptr<float>(),
      static_cast<int>(N), static_cast<int>(C), S, static_cast<int>(groups));

  return out;
}
"""

_fused_cpp_source = "torch::Tensor fused_gn_act_lse_cuda(torch::Tensor x, torch::Tensor weight, torch::Tensor bias, int64_t groups, double eps);"

_fused_ops = load_inline(
    name="fused_gn_act_lse",
    cpp_sources=_fused_cpp_source,
    cuda_sources=_fused_cuda_source,
    functions=["fused_gn_act_lse_cuda"],
    extra_cflags=["-O3"],
    extra_cuda_cflags=["-O3"],
    verbose=False,
    with_cuda=True,
)


class ModelNew(nn.Module):
    def __init__(self, in_channels, out_channels, kernel_size, groups, eps=1e-5):
        super(ModelNew, self).__init__()
        self.conv = nn.Conv2d(in_channels, out_channels, kernel_size)
        self.group_norm = nn.GroupNorm(groups, out_channels, eps=eps)
        self.tanh = nn.Tanh()
        self.hard_swish = nn.Hardswish()

    def forward(self, x):
        x_conv = self.conv(x)
        if x_conv.is_cuda and x_conv.dtype == torch.float32:
            return _fused_ops.fused_gn_act_lse_cuda(
                x_conv,
                self.group_norm.weight,
                self.group_norm.bias,
                self.group_norm.num_groups,
                self.group_norm.eps,
            )
        x_norm = self.group_norm(x_conv)
        x_tanh = self.tanh(x_norm)
        x_hard_swish = self.hard_swish(x_tanh)
        x_res = x_conv + x_hard_swish
        return torch.logsumexp(x_res, dim=1, keepdim=True)
```