@@ -155,19 +155,23 @@ __global__ void ovo_rank_dense_vs_ref_kernel(
155155// LARGE/HUGE rank kernel; LARGE smem-sorts, HUGE reads CUB-sorted groups.
156156// Post-sort mid-rank/tie body is shared and each group owns its output row.
157157template <bool SMEM_SORT >
158- __global__ void ovo_rank_sorted_kernel (
159- const float * __restrict__ ref_sorted, const float * __restrict__ grp_in,
160- const int * __restrict__ grp_offsets,
161- const double * __restrict__ ref_tie_sums, double * __restrict__ rank_sums,
162- double * __restrict__ tie_corr, int n_ref, int n_all_grp, int n_cols,
163- int n_groups, bool compute_tie_corr, int large_padded, int skip_n_grp_le) {
158+ __global__ void ovo_rank_sorted_kernel (const float * __restrict__ ref_sorted,
159+ const float * __restrict__ grp_in,
160+ const int * __restrict__ grp_offsets,
161+ const double * __restrict__ ref_tie_sums,
162+ double * __restrict__ rank_sums,
163+ double * __restrict__ tie_corr, int n_ref,
164+ int n_all_grp, int n_cols, int n_groups,
165+ bool compute_tie_corr, int large_padded,
166+ int skip_n_grp_le, int skip_n_grp_gt) {
164167 int col = blockIdx .x ;
165168 int grp = blockIdx .y ;
166169 if (col >= n_cols || grp >= n_groups) return ;
167170
168171 int g_start = grp_offsets[grp];
169172 int n_grp = grp_offsets[grp + 1 ] - g_start;
170173 if (n_grp <= skip_n_grp_le) return ;
174+ if (n_grp > skip_n_grp_gt) return ;
171175 if (n_grp == 0 ) {
172176 if (threadIdx .x == 0 ) {
173177 rank_sums[grp * n_cols + col] = 0.0 ;
@@ -214,6 +218,214 @@ __global__ void ovo_rank_sorted_kernel(
214218 &tie_corr[grp * n_cols + col]);
215219}
216220
221+ // LARGE analytic-zero path for nonnegative sparse data.
222+ // Sort only stored positives; zeros are handled from counts.
223+ __global__ void ovo_rank_smem_analytic_kernel (
224+ const float * __restrict__ ref_sorted, const float * __restrict__ grp_in,
225+ const int * __restrict__ grp_offsets,
226+ const double * __restrict__ ref_tie_sums, double * __restrict__ rank_sums,
227+ double * __restrict__ tie_corr, int n_ref, int n_all_grp, int n_cols,
228+ int n_groups, bool compute_tie_corr, int skip_n_grp_le, int skip_n_grp_gt) {
229+ int col = blockIdx .x ;
230+ int grp = blockIdx .y ;
231+ if (col >= n_cols || grp >= n_groups) return ;
232+
233+ int g_start = grp_offsets[grp];
234+ int n_grp = grp_offsets[grp + 1 ] - g_start;
235+ if (n_grp <= skip_n_grp_le) return ;
236+ if (n_grp > skip_n_grp_gt) return ;
237+ if (n_grp == 0 ) {
238+ if (threadIdx .x == 0 ) {
239+ rank_sums[grp * n_cols + col] = 0.0 ;
240+ if (compute_tie_corr) tie_corr[grp * n_cols + col] = 1.0 ;
241+ }
242+ return ;
243+ }
244+
245+ const float * ref_col = ref_sorted + (long long )col * n_ref;
246+ const float * grp_col = grp_in + (long long )col * n_all_grp + g_start;
247+
248+ extern __shared__ float grp_smem[];
249+ __shared__ double warp_buf[WARP_REDUCE_BUF ];
250+ __shared__ int sh_nnz;
251+ __shared__ int sh_ref_zeros;
252+ if (threadIdx .x == 0 ) {
253+ sh_nnz = 0 ;
254+ sh_ref_zeros = sorted_upper_bound (ref_col, 0 , n_ref, 0 .0f );
255+ }
256+ __syncthreads ();
257+
258+ for (int i = threadIdx .x ; i < n_grp; i += blockDim .x ) {
259+ float v = grp_col[i];
260+ if (v > 0 .0f ) grp_smem[atomicAdd (&sh_nnz, 1 )] = v;
261+ }
262+ __syncthreads ();
263+ int nnz = sh_nnz;
264+ int ref_zeros = sh_ref_zeros;
265+ int n_grp_zero = n_grp - nnz;
266+ int total_zero = ref_zeros + n_grp_zero;
267+
268+ // Pad positives to a power of two for bitonic_sort_smem.
269+ int padded = 1 ;
270+ while (padded < nnz) padded <<= 1 ;
271+ for (int i = nnz + threadIdx .x ; i < padded; i += blockDim .x )
272+ grp_smem[i] = __int_as_float (0x7f800000 );
273+ __syncthreads ();
274+ if (nnz > 1 ) bitonic_sort_smem (grp_smem, padded);
275+ __syncthreads ();
276+
277+ // Positive ranks are shifted by group zeros, which sort before positives.
278+ double zero_rank =
279+ (total_zero > 0 ) ? ((double )total_zero + 1.0 ) / 2.0 : 0.0 ;
280+ double local_sum =
281+ (threadIdx .x == 0 ) ? (double )n_grp_zero * zero_rank : 0.0 ;
282+ int ref_lb = 0 , ref_ub = 0 , grp_lb = 0 , grp_ub = 0 ;
283+ for (int i = threadIdx .x ; i < nnz; i += blockDim .x ) {
284+ OvoRank r = ovo_mid_rank (ref_col, n_ref, grp_smem, nnz, grp_smem[i],
285+ ref_lb, ref_ub, grp_lb, grp_ub);
286+ local_sum += r.mid_rank + (double )n_grp_zero;
287+ }
288+ double total = wilcoxon_block_sum (local_sum, warp_buf);
289+ if (threadIdx .x == 0 ) rank_sums[grp * n_cols + col] = total;
290+
291+ if (!compute_tie_corr) return ;
292+ __syncthreads ();
293+
294+ // Add nonzero tie deltas; zero ties are added below via T(ref+grp)-T(ref).
295+ double local_tie = 0.0 ;
296+ for (int i = threadIdx .x ; i < nnz; i += blockDim .x ) {
297+ if (i == 0 || grp_smem[i] != grp_smem[i - 1 ]) {
298+ float v = grp_smem[i];
299+ int gub = sorted_upper_bound (grp_smem, i + 1 , nnz, v);
300+ double cg = (double )(gub - i);
301+ int rlo = sorted_lower_bound (ref_col, 0 , n_ref, v);
302+ int rub = sorted_upper_bound (ref_col, rlo, n_ref, v);
303+ double cr = (double )(rub - rlo);
304+ double group_tie = (cg > 1.0 ) ? (cg * cg * cg - cg) : 0.0 ;
305+ local_tie += group_tie;
306+ if (cr > 0.0 ) {
307+ double comb = cr + cg;
308+ double ref_tie = (cr > 1.0 ) ? (cr * cr * cr - cr) : 0.0 ;
309+ local_tie += comb * comb * comb - comb - ref_tie - group_tie;
310+ }
311+ }
312+ }
313+ double tie = wilcoxon_block_sum (local_tie, warp_buf);
314+ if (threadIdx .x == 0 ) {
315+ double zd = 0.0 ;
316+ if (total_zero > 1 )
317+ zd += (double )total_zero * total_zero * total_zero - total_zero;
318+ if (ref_zeros > 1 )
319+ zd -= (double )ref_zeros * ref_zeros * ref_zeros - ref_zeros;
320+ tie_corr[grp * n_cols + col] =
321+ finalize_tie_corr (n_ref + n_grp, ref_tie_sums[col] + tie + zd);
322+ }
323+ }
324+
325+ // Compact HUGE-band positives and emit [base, base + nnz) segments.
326+ __global__ void compact_huge_nonzeros_kernel (
327+ const float * __restrict__ grp_dense, const int * __restrict__ grp_offsets,
328+ const int * __restrict__ group_ids, float * __restrict__ grp_nz,
329+ int * __restrict__ seg_begins, int * __restrict__ seg_ends, int n_all_grp,
330+ int n_sort_groups, int sb_cols) {
331+ int col = blockIdx .x ;
332+ int local = blockIdx .y ;
333+ if (col >= sb_cols || local >= n_sort_groups) return ;
334+ int g = group_ids[local];
335+ int g_start = grp_offsets[g];
336+ int n_grp = grp_offsets[g + 1 ] - g_start;
337+ size_t base = (size_t )col * n_all_grp + g_start;
338+ int f = col * n_sort_groups + local;
339+
340+ __shared__ int cnt;
341+ if (threadIdx .x == 0 ) cnt = 0 ;
342+ __syncthreads ();
343+ for (int i = threadIdx .x ; i < n_grp; i += blockDim .x ) {
344+ float v = grp_dense[base + i];
345+ if (v > 0 .0f ) grp_nz[base + atomicAdd (&cnt, 1 )] = v;
346+ }
347+ __syncthreads ();
348+ if (threadIdx .x == 0 ) {
349+ seg_begins[f] = (int )base;
350+ seg_ends[f] = (int )base + cnt;
351+ }
352+ }
353+
354+ // Rank HUGE-band groups from sorted positives plus the zero block.
355+ __global__ void ovo_rank_huge_analytic_kernel (
356+ const float * __restrict__ ref_sorted,
357+ const float * __restrict__ grp_nz_sorted,
358+ const int * __restrict__ grp_offsets, const int * __restrict__ group_ids,
359+ const int * __restrict__ seg_begins, const int * __restrict__ seg_ends,
360+ const double * __restrict__ ref_tie_sums, double * __restrict__ rank_sums,
361+ double * __restrict__ tie_corr, int n_ref, int n_all_grp, int n_cols,
362+ int n_sort_groups, bool compute_tie_corr) {
363+ int col = blockIdx .x ;
364+ int local = blockIdx .y ;
365+ if (col >= n_cols || local >= n_sort_groups) return ;
366+ int grp = group_ids[local];
367+ int g_start = grp_offsets[grp];
368+ int n_grp = grp_offsets[grp + 1 ] - g_start;
369+ int f = col * n_sort_groups + local;
370+ int b = seg_begins[f];
371+ int nnz = seg_ends[f] - b;
372+ const float * nz = grp_nz_sorted + b;
373+ const float * ref_col = ref_sorted + (long long )col * n_ref;
374+
375+ __shared__ double warp_buf[WARP_REDUCE_BUF ];
376+ __shared__ int sh_ref_zeros;
377+ if (threadIdx .x == 0 )
378+ sh_ref_zeros = sorted_upper_bound (ref_col, 0 , n_ref, 0 .0f );
379+ __syncthreads ();
380+ int ref_zeros = sh_ref_zeros;
381+ int n_grp_zero = n_grp - nnz;
382+ int total_zero = ref_zeros + n_grp_zero;
383+ double zero_rank =
384+ (total_zero > 0 ) ? ((double )total_zero + 1.0 ) / 2.0 : 0.0 ;
385+
386+ double local_sum =
387+ (threadIdx .x == 0 ) ? (double )n_grp_zero * zero_rank : 0.0 ;
388+ int ref_lb = 0 , ref_ub = 0 , grp_lb = 0 , grp_ub = 0 ;
389+ for (int i = threadIdx .x ; i < nnz; i += blockDim .x ) {
390+ OvoRank r = ovo_mid_rank (ref_col, n_ref, nz, nnz, nz[i], ref_lb, ref_ub,
391+ grp_lb, grp_ub);
392+ local_sum += r.mid_rank + (double )n_grp_zero;
393+ }
394+ double total = wilcoxon_block_sum (local_sum, warp_buf);
395+ if (threadIdx .x == 0 ) rank_sums[grp * n_cols + col] = total;
396+
397+ if (!compute_tie_corr) return ;
398+ __syncthreads ();
399+ double local_tie = 0.0 ;
400+ for (int i = threadIdx .x ; i < nnz; i += blockDim .x ) {
401+ if (i == 0 || nz[i] != nz[i - 1 ]) {
402+ float v = nz[i];
403+ int gub = sorted_upper_bound (nz, i + 1 , nnz, v);
404+ double cg = (double )(gub - i);
405+ int rlo = sorted_lower_bound (ref_col, 0 , n_ref, v);
406+ int rub = sorted_upper_bound (ref_col, rlo, n_ref, v);
407+ double cr = (double )(rub - rlo);
408+ double group_tie = (cg > 1.0 ) ? (cg * cg * cg - cg) : 0.0 ;
409+ local_tie += group_tie;
410+ if (cr > 0.0 ) {
411+ double comb = cr + cg;
412+ double ref_tie = (cr > 1.0 ) ? (cr * cr * cr - cr) : 0.0 ;
413+ local_tie += comb * comb * comb - comb - ref_tie - group_tie;
414+ }
415+ }
416+ }
417+ double tie = wilcoxon_block_sum (local_tie, warp_buf);
418+ if (threadIdx .x == 0 ) {
419+ double zd = 0.0 ;
420+ if (total_zero > 1 )
421+ zd += (double )total_zero * total_zero * total_zero - total_zero;
422+ if (ref_zeros > 1 )
423+ zd -= (double )ref_zeros * ref_zeros * ref_zeros - ref_zeros;
424+ tie_corr[grp * n_cols + col] =
425+ finalize_tie_corr (n_ref + n_grp, ref_tie_sums[col] + tie + zd);
426+ }
427+ }
428+
217429// MEDIUM tie helper: sorted-reference contribution, one block per column.
218430// Rank kernels add only group-only/ref-overlap deltas.
219431__global__ void ref_tie_sum_kernel (const float * __restrict__ ref_sorted,
@@ -318,6 +530,93 @@ __global__ void ovo_rank_medium_kernel(
318530 finalize_tie_corr (n_ref + n_grp, ref_tie_sums[col] + tie_delta);
319531}
320532
321- // WARP/SMALL tiers were removed; MEDIUM now covers all groups <=
322- // OVO_MEDIUM_MAX. Restore notes live in
323- // .claude/wilcoxon-warp-small-tiers-removed.md.
533+ // MEDIUM analytic-zero path for nonnegative sparse data.
534+ __global__ void ovo_rank_medium_analytic_kernel (
535+ const float * __restrict__ ref_sorted, const float * __restrict__ grp_dense,
536+ const int * __restrict__ grp_offsets,
537+ const double * __restrict__ ref_tie_sums, double * __restrict__ rank_sums,
538+ double * __restrict__ tie_corr, int n_ref, int n_all_grp, int n_cols,
539+ int n_groups, bool compute_tie_corr, int skip_n_grp_le, int max_n_grp_le) {
540+ int col = blockIdx .x ;
541+ int grp = blockIdx .y ;
542+ if (col >= n_cols || grp >= n_groups) return ;
543+
544+ int g_start = grp_offsets[grp];
545+ int n_grp = grp_offsets[grp + 1 ] - g_start;
546+ if (n_grp <= skip_n_grp_le || n_grp > max_n_grp_le) return ;
547+
548+ extern __shared__ char smem_raw[];
549+ float * grp_smem = (float *)smem_raw;
550+ double * warp_buf = (double *)(smem_raw + max_n_grp_le * sizeof (float ));
551+ __shared__ int sh_nnz;
552+ __shared__ int sh_ref_zeros;
553+
554+ const float * ref_col = ref_sorted + (long long )col * n_ref;
555+ const float * grp_col = grp_dense + (long long )col * n_all_grp + g_start;
556+ if (threadIdx .x == 0 ) {
557+ sh_nnz = 0 ;
558+ sh_ref_zeros = sorted_upper_bound (ref_col, 0 , n_ref, 0 .0f );
559+ }
560+ __syncthreads ();
561+ for (int i = threadIdx .x ; i < n_grp; i += blockDim .x ) {
562+ float v = grp_col[i];
563+ if (v > 0 .0f ) grp_smem[atomicAdd (&sh_nnz, 1 )] = v;
564+ }
565+ __syncthreads ();
566+ int nnz = sh_nnz;
567+ int ref_zeros = sh_ref_zeros;
568+ int n_grp_zero = n_grp - nnz;
569+ int total_zero = ref_zeros + n_grp_zero;
570+ double zero_rank =
571+ (total_zero > 0 ) ? ((double )total_zero + 1.0 ) / 2.0 : 0.0 ;
572+
573+ double local_sum =
574+ (threadIdx .x == 0 ) ? (double )n_grp_zero * zero_rank : 0.0 ;
575+ double local_tie = 0.0 ;
576+ for (int i = threadIdx .x ; i < nnz; i += blockDim .x ) {
577+ float v = grp_smem[i];
578+ int n_lt_ref = sorted_lower_bound (ref_col, 0 , n_ref, v);
579+ int n_eq_ref =
580+ sorted_upper_bound (ref_col, n_lt_ref, n_ref, v) - n_lt_ref;
581+ int n_lt_grp = 0 ;
582+ int n_eq_grp = 0 ;
583+ bool first_in_grp = true ;
584+ for (int j = 0 ; j < nnz; ++j) {
585+ float w = grp_smem[j];
586+ if (w < v) ++n_lt_grp;
587+ if (w == v) {
588+ ++n_eq_grp;
589+ if (j < i) first_in_grp = false ;
590+ }
591+ }
592+ local_sum += (double )(n_lt_ref + n_lt_grp + n_grp_zero) +
593+ ((double )(n_eq_ref + n_eq_grp) + 1.0 ) / 2.0 ;
594+ if (compute_tie_corr && first_in_grp) {
595+ double cg = (double )n_eq_grp;
596+ double cr = (double )n_eq_ref;
597+ double group_tie = (cg > 1.0 ) ? (cg * cg * cg - cg) : 0.0 ;
598+ local_tie += group_tie;
599+ if (cr > 0.0 ) {
600+ double combined = cr + cg;
601+ double ref_tie = (cr > 1.0 ) ? (cr * cr * cr - cr) : 0.0 ;
602+ local_tie += combined * combined * combined - combined -
603+ ref_tie - group_tie;
604+ }
605+ }
606+ }
607+ double total = wilcoxon_block_sum (local_sum, warp_buf);
608+ if (threadIdx .x == 0 ) rank_sums[grp * n_cols + col] = total;
609+
610+ if (!compute_tie_corr) return ;
611+ __syncthreads ();
612+ double tie = wilcoxon_block_sum (local_tie, warp_buf);
613+ if (threadIdx .x == 0 ) {
614+ double zd = 0.0 ;
615+ if (total_zero > 1 )
616+ zd += (double )total_zero * total_zero * total_zero - total_zero;
617+ if (ref_zeros > 1 )
618+ zd -= (double )ref_zeros * ref_zeros * ref_zeros - ref_zeros;
619+ tie_corr[grp * n_cols + col] =
620+ finalize_tie_corr (n_ref + n_grp, ref_tie_sums[col] + tie + zd);
621+ }
622+ }
0 commit comments