
    i/P                    X   d Z ddlmZ ddlmZ ddlmZ ddZ e       Z	dZ
dad ZdddZdZd	Zd
Zdad Zej&                  	 d	 	 	 	 	 	 	 	 	 	 	 dd       Z	 d	 	 	 	 	 	 	 	 	 	 	 ddZdZdad Zej&                  	 d	 	 	 	 	 	 	 	 	 	 	 dd       Z	 d	 	 	 	 	 	 	 	 	 	 	 ddZdZdad ZddZy)a5  
Custom fused Metal kernels for Demucs-MLX inference.

Each kernel includes:
- Metal source code
- Python wrapper with shape handling
- float32 accumulation for numerical stability

When Metal is not available (e.g., MLX on Linux/CPU), all functions
fall back to equivalent pure-MLX operations automatically.
    )annotationsNc                     	 t        t        dd      } | yt        | dd      }t        |      rt         |             S y# t        $ r Y yw xY w)z-Check whether Metal GPU backend is available.metalNFis_availableT)getattrmxcallablebool	Exception)r   is_avails     m/Volumes/fast/ai/experiments/voice-extract-mac/.venv/lib/python3.12/site-packages/demucs_mlx/metal_kernels.py
_has_metalr      sW    GT*=5.$7H
##  s   ? '? 	A
Aa  
uint gid = thread_position_in_grid.x;
uint total = params[0];     // N * C (total output elements)
uint half_dim = params[1];  // C (half of last dimension)

if (gid >= total) return;

uint row = gid / half_dim;
uint col = gid % half_dim;
uint full_dim = half_dim * 2;

float a = (float)x[row * full_dim + col];
float b = (float)x[row * full_dim + half_dim + col];
float sig_b = 1.0f / (1.0f + metal::exp(-b));
out[gid] = (T)(a * sig_b);
c                 n    t         *t        j                  j                  dddgdgt              a t         S )N	fused_gluxparamsoutnameinput_namesoutput_namessource)_glu_kernelr   fastmetal_kernel_GLU_SOURCE     r   _get_glu_kernelr   B   s;    gg**h	 + 
 r   c           	        t         s3t        j                  | d|      \  }}|t        j                  |      z  S | j                  }||z  }t        | j                        }||   dz  dk7  rt        d| d||          ||dz
  k7  r4t        t        |            }|d   ||   c||<   |d<    | j                  | } | j                  d   }|dz  }t        j                  | j                  d|            }	|	j                  d   }
|
|z  }t        j                  ||gt        j                        } t               |	j                  d      |gd	| j                  fg|ddft!        d
|      ddf|fg| j                  g      d   } |j                  g | j                  dd | }||dz
  k7  r4t        t        |            }|d   ||   c||<   |d<    |j                  | }|S )zFused GLU: split x in half along axis, compute a * sigmoid(b).

    Equivalent to:
        a, b = mx.split(x, 2, axis=axis)
        return a * mx.sigmoid(b)
       axisr   zAxis z size must be even, got    dtypeT   inputstemplategridthreadgroupoutput_shapesoutput_dtypesN)	HAS_METALr   splitsigmoidndimlistshape
ValueErrorrange	transpose
contiguousreshapearrayint32r   r'   min)r   r#   abr4   in_shapepermlast_dimhalfx_2dNtotalr   result_flatresults                  r   r   r   N   s    xx14(12::a=  66D$;DAGG}H~Q5&>x~>NOPP taxE$K #BxdT
DHAKKwwr{Hq=D==2x01D

1AHEXXudm2884F#/#R &).!Q]e_a+xjwwi 	K ![  5!''#2,55FtaxE$K #BxdT
DH!!!4(Mr   i   ai  
// Abramowitz & Stegun approximation of erf, max error ~1.5e-7
inline float erf_approx(float x) {
    // erf(-x) = -erf(x)
    float sign = (x >= 0.0f) ? 1.0f : -1.0f;
    float a = metal::abs(x);
    // A&S formula 7.1.26
    float t = 1.0f / (1.0f + 0.3275911f * a);
    float t2 = t * t;
    float t3 = t2 * t;
    float t4 = t3 * t;
    float t5 = t4 * t;
    float poly = 0.254829592f * t
               - 0.284496736f * t2
               + 1.421413741f * t3
               - 1.453152027f * t4
               + 1.061405429f * t5;
    float result = 1.0f - poly * metal::exp(-a * a);
    return sign * result;
}
a  
uint bg = threadgroup_position_in_grid.x;
uint tid = thread_index_in_threadgroup;
uint tg_size = threads_per_threadgroup.x;
uint sid = thread_index_in_simdgroup;
uint wid = simdgroup_index_in_threadgroup;
uint num_simdgroups = tg_size / 32;

uint num_groups = params[0];
uint channels_per_group = params[1];
uint spatial_size = params[2];
uint total_channels = params[3];

uint batch_idx = bg / num_groups;
uint group_idx = bg % num_groups;

uint elems_per_group = channels_per_group * spatial_size;

uint base = batch_idx * total_channels * spatial_size
          + group_idx * channels_per_group * spatial_size;

// Pass 1: Compute mean
float local_sum = 0.0f;
for (uint i = tid; i < elems_per_group; i += tg_size) {
    local_sum += (float)x[base + i];
}
local_sum = simd_sum(local_sum);

threadgroup float shared_sums[32];
if (sid == 0) shared_sums[wid] = local_sum;
threadgroup_barrier(mem_flags::mem_threadgroup);
if (wid == 0) {
    float val = (sid < num_simdgroups) ? shared_sums[sid] : 0.0f;
    val = simd_sum(val);
    if (sid == 0) shared_sums[0] = val;
}
threadgroup_barrier(mem_flags::mem_threadgroup);
float mean = shared_sums[0] / (float)elems_per_group;

// Pass 2: Compute variance
float local_var = 0.0f;
for (uint i = tid; i < elems_per_group; i += tg_size) {
    float diff = (float)x[base + i] - mean;
    local_var += diff * diff;
}
local_var = simd_sum(local_var);
if (sid == 0) shared_sums[wid] = local_var;
threadgroup_barrier(mem_flags::mem_threadgroup);
if (wid == 0) {
    float val = (sid < num_simdgroups) ? shared_sums[sid] : 0.0f;
    val = simd_sum(val);
    if (sid == 0) shared_sums[0] = val;
}
threadgroup_barrier(mem_flags::mem_threadgroup);
float var = shared_sums[0] / (float)elems_per_group;
float inv_std = metal::rsqrt(var + eps[0]);

// Pass 3: Normalize, apply affine, apply erf-based GELU
float rsqrt2 = 0.7071067811865475f;  // 1/sqrt(2)
for (uint i = tid; i < elems_per_group; i += tg_size) {
    uint c_local = i / spatial_size;
    uint c_global = group_idx * channels_per_group + c_local;
    float val = ((float)x[base + i] - mean) * inv_std;
    val = val * (float)weight[c_global] + (float)bias[c_global];
    // Exact GELU: 0.5 * x * (1 + erf(x / sqrt(2)))
    val = 0.5f * val * (1.0f + erf_approx(val * rsqrt2));
    out[base + i] = (T)val;
}
c                 x    t         /t        j                  j                  dg ddgt        t
              a t         S )Nfused_groupnorm_gelur   weightbiasepsr   r   )r   r   r   headerr   )_groupnorm_gelu_kernelr   r   r   _GROUPNORM_GELU_HEADER_GROUPNORM_GELU_SOURCEr   r   r   _get_groupnorm_gelu_kernelrT      s;    %!#!5!5'@)) "6 "
 "!r   c                8   | j                   d   | j                   d   }}||z  } | j                  |||g| j                   dd  }t        t        d|j                              }	|j                  |	d      }
t        j                  ||	d      }||
z
  t        j                  ||z         z  }|j                  | j                         }d|gdg| j                  dz
  z  z   }||j                  |      z  |j                  |      z   }t        j                  |      S )z.Pure-MLX GroupNorm + GELU (no Metal required).r   r$   r!   NTr#   keepdims)r6   r;   tupler8   r4   meanr   varrsqrtnngelu)r   rM   rN   
num_groupsrO   BCcpgx_raxesrY   rZ   x_normx_outw_shapes                  r   _groupnorm_gelu_fallbackrg      s     771:qwwqzqA
z/C
!))Az3
5
5Cq#((#$D888-D
&&4$
/CDjBHHS3Y//FNN177#E!fsaffqj))GFNN7++dll7.CCE775>r   c           	        t         st        | ||||      S | j                  }| j                  d   }| j                  d   }d}| j                  dd D ]  }	||	z  }	 ||z  dk7  rt        d| d|       ||z  }
|
|z  }|t        kD  rt        | ||||      S t        j                  | j                  |||            }t        j                  |j                  t
        j                              }t        j                  |j                  t
        j                              }t        j                  |gt
        j                        }t        j                  ||
||gt
        j                        }||z  }t        dt        d	|d
z   d	z  d	z              } t               |||||gd| j                  fg||z  ddf|ddf|||fg| j                  g      d   }|j                  |      S )zFused GroupNorm + GELU for NCL or NCHW layout.

    Equivalent to:
        x = groupnorm(x, weight, bias, num_groups, eps)
        x = gelu(x)
    r   r$   r!   N	channels  not divisible by num_groups r&             r(   r*   )r1   rg   r6   r7   _HYBRID_THRESHOLDr   r:   r;   astypefloat32r<   r=   r>   maxrT   r'   )r   rM   rN   r^   rO   
orig_shaper_   r`   spatial_sizedchannels_per_groupelems_per_groupx_contigeps_arrr   total_groupstgrI   s                     r   rK   rK     s    '64SIIJ	
A	
ALWWQR[  	:~9QC'DZLQRRj(<7O **'64SII}}QYYq!\:;H]]6==45F==RZZ01DhhuBJJ/GXXz#5|QGrxxXFz>L	T3r_r1b8B>?	@B)')&$8.!RA&AJ1l+,wwi 	F >>*%%r   a  
uint bg = threadgroup_position_in_grid.x;
uint tid = thread_index_in_threadgroup;
uint tg_size = threads_per_threadgroup.x;
uint sid = thread_index_in_simdgroup;
uint wid = simdgroup_index_in_threadgroup;
uint num_simdgroups = tg_size / 32;

uint num_groups = params[0];
uint channels_per_group = params[1];
uint spatial_size = params[2];
uint total_channels = params[3];
uint half_channels = params[4];

uint batch_idx = bg / num_groups;
uint group_idx = bg % num_groups;

uint elems_per_group = channels_per_group * spatial_size;

uint base = batch_idx * total_channels * spatial_size
          + group_idx * channels_per_group * spatial_size;

// Pass 1: Compute mean
float local_sum = 0.0f;
for (uint i = tid; i < elems_per_group; i += tg_size) {
    local_sum += (float)x[base + i];
}
local_sum = simd_sum(local_sum);

threadgroup float shared_sums[32];
if (sid == 0) shared_sums[wid] = local_sum;
threadgroup_barrier(mem_flags::mem_threadgroup);
if (wid == 0) {
    float val = (sid < num_simdgroups) ? shared_sums[sid] : 0.0f;
    val = simd_sum(val);
    if (sid == 0) shared_sums[0] = val;
}
threadgroup_barrier(mem_flags::mem_threadgroup);
float mean = shared_sums[0] / (float)elems_per_group;

// Pass 2: Compute variance
float local_var = 0.0f;
for (uint i = tid; i < elems_per_group; i += tg_size) {
    float diff = (float)x[base + i] - mean;
    local_var += diff * diff;
}
local_var = simd_sum(local_var);
if (sid == 0) shared_sums[wid] = local_var;
threadgroup_barrier(mem_flags::mem_threadgroup);
if (wid == 0) {
    float val = (sid < num_simdgroups) ? shared_sums[sid] : 0.0f;
    val = simd_sum(val);
    if (sid == 0) shared_sums[0] = val;
}
threadgroup_barrier(mem_flags::mem_threadgroup);
float var = shared_sums[0] / (float)elems_per_group;
float inv_std = metal::rsqrt(var + eps[0]);

// Intermediate buffer for normalized values (stored in shared memory would be
// too large for typical channel counts, so we write to a temp output and read back).
// Instead, we normalize on-the-fly and store in the output buffer.

// Pass 3: Normalize, apply affine, then apply GLU
// GLU: output[c, s] = a[c, s] * sigmoid(b[c, s])
// where a = first half of channels, b = second half
// After GroupNorm+affine, the normalized values are in (B, 2C, S) layout.
// We need to pair channel c with channel c + C.

// For GLU, we iterate over the OUTPUT elements (half the channels).
// Each output element at (c_out, s) reads:
//   a = norm_affine(x[base_a + c_out * S + s])
//   b = norm_affine(x[base_b + c_out * S + s])
// where base_a/base_b account for the group offset.

// But groups span the full 2C channels. We need to handle the case
// where the GLU split crosses group boundaries.
// For standard usage (num_groups divides 2C and C), the first half of groups
// correspond to 'a' channels and second half to 'b' channels.

// Actually for simplicity and correctness, this kernel should handle the common
// case where num_groups divides both 2C and C (e.g., groups=1 or groups=4 with
// 2C divisible by 4). The GLU split is on the channel dimension, independent of groups.

// We compute all normalized values first, then apply GLU.
// Strategy: each thread processes output elements. For each output (c_out, s),
// compute both a and b normalized values on the fly.

// Half channels per group on the output side:
uint half_cpg = channels_per_group / 2;
uint out_epg = half_cpg * spatial_size;
uint out_base = batch_idx * half_channels * spatial_size
              + group_idx * half_cpg * spatial_size;

for (uint i = tid; i < out_epg; i += tg_size) {
    uint c_local = i / spatial_size;
    uint s = i % spatial_size;

    // 'a' channel: first half of the group
    uint c_a = c_local;
    uint c_a_global = group_idx * channels_per_group + c_a;
    float val_a = ((float)x[base + c_a * spatial_size + s] - mean) * inv_std;
    val_a = val_a * (float)weight[c_a_global] + (float)bias[c_a_global];

    // 'b' channel: second half of the group
    uint c_b = c_local + half_cpg;
    uint c_b_global = group_idx * channels_per_group + c_b;
    float val_b = ((float)x[base + c_b * spatial_size + s] - mean) * inv_std;
    val_b = val_b * (float)weight[c_b_global] + (float)bias[c_b_global];

    // GLU: a * sigmoid(b)
    float sig_b = 1.0f / (1.0f + metal::exp(-val_b));
    out[out_base + i] = (T)(val_a * sig_b);
}
c                 n    t         *t        j                  j                  dg ddgt              a t         S )Nfused_groupnorm_glurL   r   r   )_groupnorm_glu_kernelr   r   r   _GROUPNORM_GLU_SOURCEr   r   r   _get_groupnorm_glu_kernelr     s8    $ " 4 4&@(	 !5 !
 ! r   c                t   | j                   d   | j                   d   }}||z  } | j                  |||g| j                   dd  }t        t        d|j                              }	|j                  |	d      }
t        j                  ||	d      }||
z
  t        j                  ||z         z  }|j                  | j                         }d|gdg| j                  dz
  z  z   }||j                  |      z  |j                  |      z   }t        j                  |dd      \  }}|t        j                  |      z  S )z-Pure-MLX GroupNorm + GLU (no Metal required).r   r$   r!   NTrV   r"   )r6   r;   rX   r8   r4   rY   r   rZ   r[   r2   r3   )r   rM   rN   r^   rO   r_   C_fullra   rb   rc   rY   rZ   rd   re   rf   normedr?   r@   s                     r   _groupnorm_glu_fallbackr     s    
AGGAJvA
J
C
!))Az3
5
5Cq#((#$D888-D
&&4$
/CDjBHHS3Y//FNN177#E&kQC166A:..GV^^G,,t||G/DDF88FAA&DAqrzz!}r   c           	        | j                   }| j                   d   }| j                   d   }|dz  }d}	| j                   dd D ]  }
|	|
z  }		 ||z  dk7  rt        d| d|       |dz  dk7  rt        d| d      ||z  }||	z  }t        r|dkD  s	|t        kD  rt	        | ||||      S t        j                  | j                  |||	            }t        j                  |j                  t
        j                              }t        j                  |j                  t
        j                              }t        j                  |gt
        j                        }t        j                  |||	||gt
        j                        }||z  }t        d	t        d
|dz   d
z  d
z              }t        |      }||d<    t               |||||gd| j                   fg||z  ddf|ddf|||	fg| j                   g      d   }|j                  |      S )a-  Fused GroupNorm + GLU for NCL or NCHW layout.

    GroupNorm normalizes over 2C channels, then GLU splits in half on axis=1.

    Equivalent to:
        x = groupnorm(x, weight, bias, num_groups, eps)
        a, b = split(x, 2, axis=1)
        return a * sigmoid(b)

    Input shape: (B, 2C, ...) -> Output shape: (B, C, ...)

    Note: Only supported when num_groups=1 or when channels_per_group is even
    (so the GLU split aligns with group boundaries). When num_groups > 1 and
    channels_per_group is odd, falls back to separate GroupNorm + GLU.
    r   r$   r!   Nri   rj   z must be even for GLUr&   rk   rl   rm   r(   r*   )r6   r7   r1   rn   r   r   r:   r;   ro   rp   r<   r=   r>   rq   r5   r   r'   )r   rM   rN   r^   rO   rr   r_   r   C_halfrs   rt   ru   rv   rw   rx   r   ry   rz   	out_shaperI   s                       r   r|   r|     s   , J	
AWWQZFq[FLWWQR[  
a9VH,I*VWWzQ9VH,ABCC:-(<7O 
Q/<M*M&q&$
CHH}}QYYq&,?@H]]6==45F==RZZ01DhhuBJJ/GXX	'vvFbhhF z>L	T3r_r1b8B>?	@BZ IIaL(&(&$8.!RA&AJ6<01wwi 	F >>)$$r   a  
uint i = thread_position_in_grid.x;
uint total = params[3];
if (i >= total) return;

uint T_dim = params[0];
uint Fr_dim = params[1];
uint C_dim = params[2];
uint FrT = Fr_dim * T_dim;
uint CFrT = C_dim * FrT;

uint b = i / (2 * CFrT);
uint rem = i % (2 * CFrT);
uint c2 = rem / FrT;
uint c = c2 / 2;
uint is_imag = c2 % 2;
uint ft = rem % FrT;

uint in_idx = (b * CFrT + c * FrT + ft) * 2 + is_imag;
out[i] = x[in_idx];
c                 n    t         *t        j                  j                  dddgdgt              a t         S )Ncomplex_to_interleavedr   r   r   r   )_complex_to_interleaved_kernelr   r   r   _COMPLEX_TO_INTERLEAVED_SOURCEr   r   r   "_get_complex_to_interleaved_kernelr   E  s<    %-)+)=)=)h1	 *> *
& *)r   c           	        | j                   \  }}}}t        sXt        j                  |       }t        j                  |       }t        j
                  ||gd      j                  ||dz  ||      S t        j                  | t        j                        }t        j                  |      j                  d      }|dz  |z  |z  |z  }	t        j                  ||||	gt        j                        }
 t               ||
gg |	ddft        d|	      ddf|	fgt        j                  g      d   }|j                  |d|z  ||      S )	zConvert complex (B, C, Fr, T) to interleaved real (B, 2*C, Fr, T).

    Equivalent to:
        real = mx.real(z)
        imag = mx.imag(z)
        m = mx.stack([real, imag], axis=2).reshape(B, C*2, Fr, T)
    r!   r"   r%   r&   r$   r)   r*   r   )r6   r1   r   realimagstackr;   viewrp   r:   r<   r=   r   r>   )zr_   r`   Frr(   r   r   z_realz_flatrG   r   rI   s               r   fused_complex_to_interleavedr   Q  s%    ''KAq"awwqzwwqzxxt1-55aQAFFWWQ

#F]]6"**2.FEAINQEXXq"a'rxx8F1/1Q]e_a+xjzzl 	F >>!QUB**r   )returnr
   )r$   )r   mx.arrayr#   intr   r   )gh㈵>)r   r   rM   r   rN   r   r^   r   rO   floatr   r   )r   r   r   r   )__doc__
__future__r   mlx.corecorer   mlx.nnr\   r   r1   r   r   r   r   rn   rR   rS   rQ   rT   compilerg   rK   r~   r}   r   r   r|   r   r   r   r   r   r   r   <module>r      s  
 #  " L	" 	0j   ,D L  
"  #'!)1  . 3&3&3& 3& 	3&
 
3& 3&zq f  	!  #'!)1  0 E%E%E% E% 	E%
 
E% E%X" , "& 	*+r   