///|
/// `erff` from libm. MoonBit's `@math` has no error function, and GELU in both
/// ModernBERT and the Laya heads is the exact `erf` form rather than the `tanh`
/// approximation, so an approximation here would show up as a real parity gap.
extern "c" fn erff(x : Float) -> Float = "erff"

///|
/// Allocate a zeroed activation buffer.
pub fn zeros(len : Int) -> FixedArray[Float] {
  FixedArray::make(len, 0.0)
}

///|
/// Layer normalisation over the last axis of a `[rows, cols]` buffer.
///
/// Means and variances accumulate in `Double` — at `cols = 1024` the float32
/// sums drift enough to be visible against the reference.
pub fn layer_norm(
  x : FixedArray[Float],
  out : FixedArray[Float],
  rows~ : Int,
  cols~ : Int,
  gamma : FixedArray[Float],
  beta : FixedArray[Float]?,
  eps~ : Float,
) -> Unit {
  let eps = eps.to_double()
  for row in 0..
        for i in 0..
        for i in 0.. Float {
  0.5 * x * (1.0 + erff(x * Float::from_double(1.0 / Double::sqrt(2.0))))
}

///|
/// GeGLU over a `[rows, 2 · inner]` buffer produced by ModernBERT's `Wi`.
///
/// `Wi` emits the value and gate halves concatenated along the last axis;
/// `out[rows, inner] = gelu(value) · gate`.
pub fn geglu(
  x : FixedArray[Float],
  out : FixedArray[Float],
  rows~ : Int,
  inner~ : Int,
) -> Unit {
  for row in 0.. Unit {
  for i in 0.. Unit {
  for i in 0.. Unit {
  for i in 0.. Unit {
  let half = dim / 2
  let inv_freq = FixedArray::make(half, 0.0)
  for i in 0.. Unit {
  for row in 0.. max {
        max = x[base + i]
      }
    }
    let mut total = 0.0
    for i in 0.. Array[Double] {
  let mut max = logits[0]
  for value in logits {
    if value > max {
      max = value
    }
  }
  let out = Array::make(logits.length(), 0.0)
  let mut total = 0.0
  for i, value in logits {
    let e = @math.exp(value - max)
    out[i] = e
    total += e
  }
  for i in 0..