cumo 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +590 -152
  3. data/CHANGELOG.md +29 -0
  4. data/README.md +140 -0
  5. data/bench/cumo_probe.rb +1 -1
  6. data/cumo.gemspec +1 -1
  7. data/ext/cumo/cuda/cudnn.c +18 -0
  8. data/ext/cumo/cuda/cudnn_impl.cpp +60 -14
  9. data/ext/cumo/cuda/driver.c +11 -9
  10. data/ext/cumo/cuda/runtime.c +56 -0
  11. data/ext/cumo/cumo.c +4 -0
  12. data/ext/cumo/extconf.rb +2 -0
  13. data/ext/cumo/include/cumo/bit_reduce_kernel.h +6 -6
  14. data/ext/cumo/include/cumo/cuda/cudnn.h +40 -0
  15. data/ext/cumo/include/cumo/cuda/runtime.h +2 -0
  16. data/ext/cumo/include/cumo/intern.h +9 -0
  17. data/ext/cumo/include/cumo/narray.h +1 -0
  18. data/ext/cumo/include/cumo/reduce_kernel.h +32 -28
  19. data/ext/cumo/include/cumo/row_kernel.h +170 -0
  20. data/ext/cumo/include/cumo/row_method.h +48 -0
  21. data/ext/cumo/include/cumo/template_kernel.h +26 -0
  22. data/ext/cumo/include/cumo/types/bf16_def.h +82 -0
  23. data/ext/cumo/include/cumo/types/bf16_def_kernel.h +28 -0
  24. data/ext/cumo/include/cumo/types/bf16_macro.h +186 -0
  25. data/ext/cumo/include/cumo/types/bf16_macro_kernel.h +213 -0
  26. data/ext/cumo/include/cumo/types/bfloat.h +22 -0
  27. data/ext/cumo/include/cumo/types/bfloat_kernel.h +16 -0
  28. data/ext/cumo/include/cumo/types/bit.h +2 -0
  29. data/ext/cumo/include/cumo/types/bit_kernel.h +2 -0
  30. data/ext/cumo/include/cumo/types/complex_macro.h +2 -0
  31. data/ext/cumo/include/cumo/types/complex_macro_kernel.h +2 -0
  32. data/ext/cumo/include/cumo/types/float_def.h +8 -0
  33. data/ext/cumo/include/cumo/types/float_def_kernel.h +8 -0
  34. data/ext/cumo/include/cumo/types/float_macro.h +19 -17
  35. data/ext/cumo/include/cumo/types/float_macro_kernel.h +19 -19
  36. data/ext/cumo/include/cumo/types/half_macro.h +15 -17
  37. data/ext/cumo/include/cumo/types/half_macro_kernel.h +15 -0
  38. data/ext/cumo/include/cumo/types/robj_macro.h +2 -0
  39. data/ext/cumo/include/cumo/types/xint_macro.h +2 -6
  40. data/ext/cumo/include/cumo/types/xint_macro_kernel.h +2 -0
  41. data/ext/cumo/include/cumo.h +2 -2
  42. data/ext/cumo/narray/gen/def/bfloat.rb +56 -0
  43. data/ext/cumo/narray/gen/def/bit.rb +11 -1
  44. data/ext/cumo/narray/gen/def/dcomplex.rb +11 -1
  45. data/ext/cumo/narray/gen/def/dfloat.rb +10 -1
  46. data/ext/cumo/narray/gen/def/hfloat.rb +10 -1
  47. data/ext/cumo/narray/gen/def/int16.rb +11 -1
  48. data/ext/cumo/narray/gen/def/int32.rb +11 -1
  49. data/ext/cumo/narray/gen/def/int64.rb +11 -1
  50. data/ext/cumo/narray/gen/def/int8.rb +11 -1
  51. data/ext/cumo/narray/gen/def/robject.rb +11 -1
  52. data/ext/cumo/narray/gen/def/scomplex.rb +11 -1
  53. data/ext/cumo/narray/gen/def/sfloat.rb +10 -1
  54. data/ext/cumo/narray/gen/def/uint16.rb +11 -1
  55. data/ext/cumo/narray/gen/def/uint32.rb +11 -1
  56. data/ext/cumo/narray/gen/def/uint64.rb +11 -1
  57. data/ext/cumo/narray/gen/def/uint8.rb +11 -1
  58. data/ext/cumo/narray/gen/narray_def.rb +0 -5
  59. data/ext/cumo/narray/gen/spec.rb +11 -14
  60. data/ext/cumo/narray/gen/tmpl/accum_arg.c +1 -1
  61. data/ext/cumo/narray/gen/tmpl/accum_binary_kernel.cu +9 -9
  62. data/ext/cumo/narray/gen/tmpl/accum_index.c +1 -1
  63. data/ext/cumo/narray/gen/tmpl/batch_norm.c +3 -3
  64. data/ext/cumo/narray/gen/tmpl/batch_norm_backward.c +3 -3
  65. data/ext/cumo/narray/gen/tmpl/complex_accum_kernel.cu +15 -5
  66. data/ext/cumo/narray/gen/tmpl/conv.c +88 -121
  67. data/ext/cumo/narray/gen/tmpl/conv_grad_w.c +89 -81
  68. data/ext/cumo/narray/gen/tmpl/conv_transpose.c +88 -121
  69. data/ext/cumo/narray/gen/tmpl/cum.c +8 -7
  70. data/ext/cumo/narray/gen/tmpl/cum_kernel.cu +7 -6
  71. data/ext/cumo/narray/gen/tmpl/fixed_batch_norm.c +3 -3
  72. data/ext/cumo/narray/gen/tmpl/float_accum_kernel.cu +22 -10
  73. data/ext/cumo/narray/gen/tmpl/gemm.c +11 -35
  74. data/ext/cumo/narray/gen/tmpl/layer_norm.c +91 -0
  75. data/ext/cumo/narray/gen/tmpl/layer_norm_kernel.cu +54 -0
  76. data/ext/cumo/narray/gen/tmpl/lib.c +1 -0
  77. data/ext/cumo/narray/gen/tmpl/lib_kernel.cu +1 -0
  78. data/ext/cumo/narray/gen/tmpl/median.c +1 -1
  79. data/ext/cumo/narray/gen/tmpl/poly.c +0 -3
  80. data/ext/cumo/narray/gen/tmpl/poly_kernel.cu +1 -1
  81. data/ext/cumo/narray/gen/tmpl/pooling_backward.c +2 -1
  82. data/ext/cumo/narray/gen/tmpl/pooling_forward.c +2 -1
  83. data/ext/cumo/narray/gen/tmpl/rand.c +9 -9
  84. data/ext/cumo/narray/gen/tmpl/rand_kernel.cu +9 -9
  85. data/ext/cumo/narray/gen/tmpl/rand_norm.c +4 -4
  86. data/ext/cumo/narray/gen/tmpl/rand_norm_kernel.cu +7 -7
  87. data/ext/cumo/narray/gen/tmpl/real_accum_kernel.cu +6 -6
  88. data/ext/cumo/narray/gen/tmpl/softmax.c +56 -0
  89. data/ext/cumo/narray/gen/tmpl/softmax_kernel.cu +157 -0
  90. data/ext/cumo/narray/gen/tmpl/sort.c +9 -32
  91. data/ext/cumo/narray/gen/tmpl/sort_index.c +16 -95
  92. data/ext/cumo/narray/gen/tmpl/store_array.c +12 -9
  93. data/ext/cumo/narray/gen/tmpl_bit/mask.c +11 -11
  94. data/ext/cumo/narray/gen/tmpl_bit/store_array.c +5 -9
  95. data/ext/cumo/narray/gen/tmpl_bit/where.c +50 -11
  96. data/ext/cumo/narray/gen/tmpl_bit/where2.c +11 -12
  97. data/ext/cumo/narray/gen/tmpl_bit/where_kernel.cu +1 -2
  98. data/ext/cumo/narray/index.c +2 -1
  99. data/ext/cumo/narray/math.c +2 -0
  100. data/ext/cumo/narray/narray.c +27 -1
  101. data/ext/cumo/narray/ndloop.c +73 -4
  102. data/ext/cumo/narray/sort_kernel.cu +61 -41
  103. data/ext/cumo/narray/struct.c +3 -0
  104. data/lib/cumo/cuda/cudnn.rb +1 -1
  105. data/lib/cumo/narray/extra.rb +2 -2
  106. data/test/bfloat_test.rb +238 -0
  107. data/test/cuda/memory_pool_test.rb +8 -7
  108. data/test/cuda/runtime_test.rb +25 -0
  109. data/test/cudnn_test.rb +108 -24
  110. data/test/extra_test.rb +1 -1
  111. data/test/fused_test.rb +311 -0
  112. data/test/math_test.rb +77 -2
  113. data/test/narray_alt_coverage_test.rb +1 -0
  114. data/test/narray_test.rb +70 -10
  115. data/test/test_helper.rb +1 -0
  116. metadata +20 -4
  117. data/ext/cumo/narray/gen/tmpl/qsort.c +0 -205
@@ -190,6 +190,7 @@ extern VALUE cumo_cBit;
190
190
  extern VALUE cumo_cDFloat;
191
191
  extern VALUE cumo_cSFloat;
192
192
  extern VALUE cumo_cHFloat;
193
+ extern VALUE cumo_cBFloat;
193
194
  extern VALUE cumo_cDComplex;
194
195
  extern VALUE cumo_cSComplex;
195
196
  extern VALUE cumo_cInt64;
@@ -602,7 +602,7 @@ struct reduce_combine {
602
602
  // the partials so the caller can combine them with its own second pass. The
603
603
  // caller frees the returned buffer.
604
604
  template <typename TypeIn, typename TypeReduce, typename ReductionImpl, bool ARG = false>
605
- TypeReduce* reduce_partial_pass(cumo_na_reduction_arg_t arg, cumo_reduce_addr_t ad, int64_t n_split, int64_t reduce_total_size, cumo_na_reduction_arg_t* arg2, ReductionImpl& impl) {
605
+ TypeReduce* reduce_partial_pass(cumo_na_reduction_arg_t arg, cumo_reduce_addr_t ad, int64_t n_split, int64_t reduce_total_size, cumo_na_reduction_arg_t* arg2, ReductionImpl& impl, char* held = 0) {
606
606
  int64_t chunk = (reduce_total_size + n_split - 1) / n_split;
607
607
  int64_t partial_total_size = arg.out_indexer.total_size * n_split;
608
608
  TypeReduce* partial = reinterpret_cast<TypeReduce*>(cumo_cuda_runtime_malloc(sizeof(TypeReduce) * partial_total_size));
@@ -618,7 +618,7 @@ TypeReduce* reduce_partial_pass(cumo_na_reduction_arg_t arg, cumo_reduce_addr_t
618
618
  } else {
619
619
  reduction_partial_kernel<false,ARG,TypeIn,TypeReduce,ReductionImpl><<<grid_size, max_block_size, shared_mem_size>>>(arg, ad, partial, n_split, chunk, out_block_size, reduce_block_size, impl);
620
620
  }
621
- cumo_cuda_runtime_check_kernel_launch();
621
+ cumo_check_launch_holding(partial, held);
622
622
 
623
623
  arg2->in.ptr = reinterpret_cast<char*>(partial);
624
624
  arg2->in.step[0] = sizeof(TypeReduce);
@@ -636,7 +636,7 @@ static inline bool zip_axes_are_flat(const cumo_reduce_addr_t& ad, const cumo_re
636
636
 
637
637
  // First pass of a split zip reduction. See reduce_partial_pass above.
638
638
  template <typename TypeIn, typename TypeReduce, typename ReductionImpl>
639
- TypeReduce* reduce_zip_partial_pass(cumo_na_reduction_arg_t arg, cumo_na_iarray_t in2, cumo_reduce_addr_t ad, cumo_reduce_addr_t ad2, int64_t n_split, int64_t reduce_total_size, cumo_na_reduction_arg_t* arg2, ReductionImpl& impl) {
639
+ TypeReduce* reduce_zip_partial_pass(cumo_na_reduction_arg_t arg, cumo_na_iarray_t in2, cumo_reduce_addr_t ad, cumo_reduce_addr_t ad2, int64_t n_split, int64_t reduce_total_size, cumo_na_reduction_arg_t* arg2, ReductionImpl& impl, char* held = 0) {
640
640
  int64_t chunk = (reduce_total_size + n_split - 1) / n_split;
641
641
  int64_t partial_total_size = arg.out_indexer.total_size * n_split;
642
642
  TypeReduce* partial = reinterpret_cast<TypeReduce*>(cumo_cuda_runtime_malloc(sizeof(TypeReduce) * partial_total_size));
@@ -652,7 +652,7 @@ TypeReduce* reduce_zip_partial_pass(cumo_na_reduction_arg_t arg, cumo_na_iarray_
652
652
  } else {
653
653
  reduction_zip_partial_kernel<false,TypeIn,TypeReduce,ReductionImpl><<<grid_size, max_block_size, shared_mem_size>>>(arg, in2, ad, ad2, partial, n_split, chunk, out_block_size, reduce_block_size, impl);
654
654
  }
655
- cumo_cuda_runtime_check_kernel_launch();
655
+ cumo_check_launch_holding(partial, held);
656
656
 
657
657
  arg2->in.ptr = reinterpret_cast<char*>(partial);
658
658
  arg2->in.step[0] = sizeof(TypeReduce);
@@ -664,8 +664,12 @@ TypeReduce* reduce_zip_partial_pass(cumo_na_reduction_arg_t arg, cumo_na_iarray_
664
664
 
665
665
  } // cumo_detail
666
666
 
667
+ // held0 and held1 are every scratch buffer still outstanding, the caller's
668
+ // included. The checks free them before they raise, since rb_raise is a longjmp
669
+ // and nothing written after it runs. The split forms pass their own partial as
670
+ // well as whatever they were handed.
667
671
  template <typename TypeIn, typename TypeOut, typename ReductionImpl>
668
- void cumo_reduce(cumo_na_reduction_arg_t arg, ReductionImpl&& impl) {
672
+ void cumo_reduce(cumo_na_reduction_arg_t arg, ReductionImpl&& impl, char* held0 = 0, char* held1 = 0) {
669
673
  if (arg.out_indexer.total_size == 0) {
670
674
  return;
671
675
  }
@@ -686,7 +690,7 @@ void cumo_reduce(cumo_na_reduction_arg_t arg, ReductionImpl&& impl) {
686
690
  } else {
687
691
  cumo_detail::reduction_kernel<false,TypeIn,TypeOut,ReductionImpl><<<grid_size, block_size, shared_mem_size>>>(arg, ad, out_block_size, reduce_block_size, impl);
688
692
  }
689
- cumo_cuda_runtime_check_kernel_launch();
693
+ cumo_check_launch_holding(held0, held1);
690
694
  }
691
695
 
692
696
  // Runs the reduce axis of one output across several blocks and combines their
@@ -695,7 +699,7 @@ void cumo_reduce(cumo_na_reduction_arg_t arg, ReductionImpl&& impl) {
695
699
  // case. Only for an impl whose Identity ignores its index argument — see
696
700
  // reduce_combine above.
697
701
  template <typename TypeIn, typename TypeOut, typename ReductionImpl>
698
- void cumo_reduce_split(cumo_na_reduction_arg_t arg, ReductionImpl&& impl) {
702
+ void cumo_reduce_split(cumo_na_reduction_arg_t arg, ReductionImpl&& impl, char* held = 0) {
699
703
  using TypeReduce = decltype(impl.Identity(0));
700
704
 
701
705
  if (arg.out_indexer.total_size == 0) {
@@ -711,20 +715,20 @@ void cumo_reduce_split(cumo_na_reduction_arg_t arg, ReductionImpl&& impl) {
711
715
 
712
716
  int64_t n_split = cumo_detail::reduce_split_count(reduce_total_size, out_block_num);
713
717
  if (n_split < 2) {
714
- cumo_reduce<TypeIn, TypeOut, ReductionImpl>(arg, std::forward<ReductionImpl>(impl));
718
+ cumo_reduce<TypeIn, TypeOut, ReductionImpl>(arg, std::forward<ReductionImpl>(impl), held);
715
719
  return;
716
720
  }
717
721
 
718
722
  cumo_na_reduction_arg_t arg2 = arg;
719
- TypeReduce* partial = cumo_detail::reduce_partial_pass<TypeIn, TypeReduce, ReductionImpl>(arg, ad, n_split, reduce_total_size, &arg2, impl);
720
- cumo_reduce<TypeReduce, TypeOut, cumo_detail::reduce_combine<ReductionImpl>>(arg2, cumo_detail::reduce_combine<ReductionImpl>{impl});
723
+ TypeReduce* partial = cumo_detail::reduce_partial_pass<TypeIn, TypeReduce, ReductionImpl>(arg, ad, n_split, reduce_total_size, &arg2, impl, held);
724
+ cumo_reduce<TypeReduce, TypeOut, cumo_detail::reduce_combine<ReductionImpl>>(arg2, cumo_detail::reduce_combine<ReductionImpl>{impl}, reinterpret_cast<char*>(partial), held);
721
725
  cumo_cuda_runtime_free(reinterpret_cast<char*>(partial));
722
726
  }
723
727
 
724
728
  // Variant of cumo_reduce reading two inputs, for mulsum. in2 describes the same
725
729
  // shape as arg.in, since the one in_indexer addresses both.
726
730
  template <typename TypeIn, typename TypeOut, typename ReductionImpl>
727
- void cumo_reduce_zip(cumo_na_reduction_arg_t arg, cumo_na_iarray_t in2, ReductionImpl&& impl) {
731
+ void cumo_reduce_zip(cumo_na_reduction_arg_t arg, cumo_na_iarray_t in2, ReductionImpl&& impl, char* held0 = 0, char* held1 = 0) {
728
732
  if (arg.out_indexer.total_size == 0) {
729
733
  return;
730
734
  }
@@ -748,13 +752,13 @@ void cumo_reduce_zip(cumo_na_reduction_arg_t arg, cumo_na_iarray_t in2, Reductio
748
752
  } else {
749
753
  cumo_detail::reduction_zip_kernel<false,TypeIn,TypeOut,ReductionImpl><<<grid_size, block_size, shared_mem_size>>>(arg, in2, ad, ad2, out_block_size, reduce_block_size, impl);
750
754
  }
751
- cumo_cuda_runtime_check_kernel_launch();
755
+ cumo_check_launch_holding(held0, held1);
752
756
  }
753
757
 
754
758
  // cumo_reduce_split for a zip reduction. The first pass reads both operands and
755
759
  // the combine pass has only accumulators left, so it is the plain one.
756
760
  template <typename TypeIn, typename TypeOut, typename ReductionImpl>
757
- void cumo_reduce_zip_split(cumo_na_reduction_arg_t arg, cumo_na_iarray_t in2, ReductionImpl&& impl) {
761
+ void cumo_reduce_zip_split(cumo_na_reduction_arg_t arg, cumo_na_iarray_t in2, ReductionImpl&& impl, char* held = 0) {
758
762
  using TypeReduce = decltype(impl.Identity(0));
759
763
 
760
764
  if (arg.out_indexer.total_size == 0) {
@@ -773,13 +777,13 @@ void cumo_reduce_zip_split(cumo_na_reduction_arg_t arg, cumo_na_iarray_t in2, Re
773
777
 
774
778
  int64_t n_split = cumo_detail::reduce_split_count(reduce_total_size, out_block_num);
775
779
  if (n_split < 2) {
776
- cumo_reduce_zip<TypeIn, TypeOut, ReductionImpl>(arg, in2, std::forward<ReductionImpl>(impl));
780
+ cumo_reduce_zip<TypeIn, TypeOut, ReductionImpl>(arg, in2, std::forward<ReductionImpl>(impl), held);
777
781
  return;
778
782
  }
779
783
 
780
784
  cumo_na_reduction_arg_t combine = arg;
781
- TypeReduce* partial = cumo_detail::reduce_zip_partial_pass<TypeIn, TypeReduce, ReductionImpl>(arg, in2, ad, ad2, n_split, reduce_total_size, &combine, impl);
782
- cumo_reduce<TypeReduce, TypeOut, cumo_detail::reduce_combine<ReductionImpl>>(combine, cumo_detail::reduce_combine<ReductionImpl>{impl});
785
+ TypeReduce* partial = cumo_detail::reduce_zip_partial_pass<TypeIn, TypeReduce, ReductionImpl>(arg, in2, ad, ad2, n_split, reduce_total_size, &combine, impl, held);
786
+ cumo_reduce<TypeReduce, TypeOut, cumo_detail::reduce_combine<ReductionImpl>>(combine, cumo_detail::reduce_combine<ReductionImpl>{impl}, reinterpret_cast<char*>(partial), held);
783
787
  cumo_cuda_runtime_free(reinterpret_cast<char*>(partial));
784
788
  }
785
789
 
@@ -787,7 +791,7 @@ void cumo_reduce_zip_split(cumo_na_reduction_arg_t arg, cumo_na_iarray_t in2, Re
787
791
  // See reduction_pair_kernel above. out2 has to describe the same shape as
788
792
  // arg.out, since the one out_indexer addresses both.
789
793
  template <typename TypeIn, typename TypeOut, typename ReductionImpl>
790
- void cumo_reduce_pair(cumo_na_reduction_arg_t arg, cumo_na_iarray_t out2, ReductionImpl&& impl) {
794
+ void cumo_reduce_pair(cumo_na_reduction_arg_t arg, cumo_na_iarray_t out2, ReductionImpl&& impl, char* held0 = 0, char* held1 = 0) {
791
795
  if (arg.out_indexer.total_size == 0) {
792
796
  return;
793
797
  }
@@ -809,12 +813,12 @@ void cumo_reduce_pair(cumo_na_reduction_arg_t arg, cumo_na_iarray_t out2, Reduct
809
813
  } else {
810
814
  cumo_detail::reduction_pair_kernel<false,TypeIn,TypeOut,ReductionImpl><<<grid_size, block_size, shared_mem_size>>>(arg, out2, ad, out_block_size, reduce_block_size, impl);
811
815
  }
812
- cumo_cuda_runtime_check_kernel_launch();
816
+ cumo_check_launch_holding(held0, held1);
813
817
  }
814
818
 
815
819
  // cumo_reduce_split for the two-output form. Same constraint on Identity.
816
820
  template <typename TypeIn, typename TypeOut, typename ReductionImpl>
817
- void cumo_reduce_pair_split(cumo_na_reduction_arg_t arg, cumo_na_iarray_t out2, ReductionImpl&& impl) {
821
+ void cumo_reduce_pair_split(cumo_na_reduction_arg_t arg, cumo_na_iarray_t out2, ReductionImpl&& impl, char* held = 0) {
818
822
  using TypeReduce = decltype(impl.Identity(0));
819
823
 
820
824
  if (arg.out_indexer.total_size == 0) {
@@ -830,20 +834,20 @@ void cumo_reduce_pair_split(cumo_na_reduction_arg_t arg, cumo_na_iarray_t out2,
830
834
 
831
835
  int64_t n_split = cumo_detail::reduce_split_count(reduce_total_size, out_block_num);
832
836
  if (n_split < 2) {
833
- cumo_reduce_pair<TypeIn, TypeOut, ReductionImpl>(arg, out2, std::forward<ReductionImpl>(impl));
837
+ cumo_reduce_pair<TypeIn, TypeOut, ReductionImpl>(arg, out2, std::forward<ReductionImpl>(impl), held);
834
838
  return;
835
839
  }
836
840
 
837
841
  cumo_na_reduction_arg_t arg2 = arg;
838
- TypeReduce* partial = cumo_detail::reduce_partial_pass<TypeIn, TypeReduce, ReductionImpl>(arg, ad, n_split, reduce_total_size, &arg2, impl);
839
- cumo_reduce_pair<TypeReduce, TypeOut, cumo_detail::reduce_combine<ReductionImpl>>(arg2, out2, cumo_detail::reduce_combine<ReductionImpl>{impl});
842
+ TypeReduce* partial = cumo_detail::reduce_partial_pass<TypeIn, TypeReduce, ReductionImpl>(arg, ad, n_split, reduce_total_size, &arg2, impl, held);
843
+ cumo_reduce_pair<TypeReduce, TypeOut, cumo_detail::reduce_combine<ReductionImpl>>(arg2, out2, cumo_detail::reduce_combine<ReductionImpl>{impl}, reinterpret_cast<char*>(partial), held);
840
844
  cumo_cuda_runtime_free(reinterpret_cast<char*>(partial));
841
845
  }
842
846
 
843
847
  // Variant of cumo_reduce for arg-reductions (argmax/argmin), which returns
844
848
  // indices along the reduction axis. See reduction_arg_kernel above.
845
849
  template <typename TypeIn, typename TypeOut, typename ReductionImpl>
846
- void cumo_reduce_arg(cumo_na_reduction_arg_t arg, ReductionImpl&& impl) {
850
+ void cumo_reduce_arg(cumo_na_reduction_arg_t arg, ReductionImpl&& impl, char* held0 = 0, char* held1 = 0) {
847
851
  if (arg.out_indexer.total_size == 0) {
848
852
  return;
849
853
  }
@@ -864,14 +868,14 @@ void cumo_reduce_arg(cumo_na_reduction_arg_t arg, ReductionImpl&& impl) {
864
868
  } else {
865
869
  cumo_detail::reduction_arg_kernel<false,TypeIn,TypeOut,ReductionImpl><<<grid_size, block_size, shared_mem_size>>>(arg, ad, out_block_size, reduce_block_size, impl);
866
870
  }
867
- cumo_cuda_runtime_check_kernel_launch();
871
+ cumo_check_launch_holding(held0, held1);
868
872
  }
869
873
 
870
874
  // cumo_reduce_split for the arg form. The partials already carry indices along
871
875
  // the reduce axis, so the second pass combines them with the plain kernel.
872
876
  // Only for an impl whose Identity ignores its index argument.
873
877
  template <typename TypeIn, typename TypeOut, typename ReductionImpl>
874
- void cumo_reduce_arg_split(cumo_na_reduction_arg_t arg, ReductionImpl&& impl) {
878
+ void cumo_reduce_arg_split(cumo_na_reduction_arg_t arg, ReductionImpl&& impl, char* held = 0) {
875
879
  using TypeReduce = decltype(impl.Identity(0));
876
880
 
877
881
  if (arg.out_indexer.total_size == 0) {
@@ -887,13 +891,13 @@ void cumo_reduce_arg_split(cumo_na_reduction_arg_t arg, ReductionImpl&& impl) {
887
891
 
888
892
  int64_t n_split = cumo_detail::reduce_split_count(reduce_total_size, out_block_num);
889
893
  if (n_split < 2) {
890
- cumo_reduce_arg<TypeIn, TypeOut, ReductionImpl>(arg, std::forward<ReductionImpl>(impl));
894
+ cumo_reduce_arg<TypeIn, TypeOut, ReductionImpl>(arg, std::forward<ReductionImpl>(impl), held);
891
895
  return;
892
896
  }
893
897
 
894
898
  cumo_na_reduction_arg_t arg2 = arg;
895
- TypeReduce* partial = cumo_detail::reduce_partial_pass<TypeIn, TypeReduce, ReductionImpl, true>(arg, ad, n_split, reduce_total_size, &arg2, impl);
896
- cumo_reduce<TypeReduce, TypeOut, cumo_detail::reduce_combine<ReductionImpl>>(arg2, cumo_detail::reduce_combine<ReductionImpl>{impl});
899
+ TypeReduce* partial = cumo_detail::reduce_partial_pass<TypeIn, TypeReduce, ReductionImpl, true>(arg, ad, n_split, reduce_total_size, &arg2, impl, held);
900
+ cumo_reduce<TypeReduce, TypeOut, cumo_detail::reduce_combine<ReductionImpl>>(arg2, cumo_detail::reduce_combine<ReductionImpl>{impl}, reinterpret_cast<char*>(partial), held);
897
901
  cumo_cuda_runtime_free(reinterpret_cast<char*>(partial));
898
902
  }
899
903
 
@@ -0,0 +1,170 @@
1
+ #ifndef CUMO_ROW_KERNEL_H
2
+ #define CUMO_ROW_KERNEL_H
3
+
4
+ #include "cumo/reduce_kernel.h"
5
+
6
+ // Reducing each row of an array to a few numbers and then walking the row again
7
+ // to write it out is the shape layer norm and softmax share. One block takes one
8
+ // row, so what the reduction found stays in shared memory between the two passes
9
+ // instead of going back to global memory as an array of its own: written out of
10
+ // mean, minus, divide and the rest, a layer norm costs nine kernels and eight
11
+ // intermediate arrays.
12
+ //
13
+ // Impl is a reduction impl as reduce_kernel.h means it, whose MapOut answers
14
+ // whatever the second pass needs. Apply turns one element into one element,
15
+ // taking its position along the row so that a per-column operand can be read.
16
+ //
17
+ // Two things Impl has to hold to, neither of which the compiler can check.
18
+ // It must not read the index its Identity and MapIn are handed: the kernel
19
+ // below always passes 0, while the split path passes the element's real offset,
20
+ // so an impl that reads it answers differently depending on which path a shape
21
+ // takes. And it must be default constructible, since cumo_reduce_split's
22
+ // reduce_combine names its accumulator as decltype(Impl().Identity(0)).
23
+
24
+ namespace cumo_detail {
25
+
26
+ template <typename TypeIn, typename Impl, typename Apply>
27
+ __global__ void row_reduce_apply_kernel(
28
+ const TypeIn* x, TypeIn* y, uint64_t rows, uint64_t cols, Impl impl, Apply apply)
29
+ {
30
+ typedef decltype(impl.Identity(0)) Accum;
31
+ static_assert(alignof(Accum) <= 8,
32
+ "the shared memory below is declared __align__(8), as every other "
33
+ "dynamic one in cumo is, so a wider accumulator would land unaligned");
34
+
35
+ extern __shared__ __align__(8) char cumo_row_smem[];
36
+ Accum* sdata = reinterpret_cast<Accum*>(cumo_row_smem);
37
+ unsigned int tid = threadIdx.x;
38
+
39
+ for (uint64_t row = blockIdx.x; row < rows; row += gridDim.x) {
40
+ const TypeIn* xr = x + row * cols;
41
+ TypeIn* yr = y + row * cols;
42
+ Accum accum = impl.Identity(0);
43
+
44
+ for (uint64_t i = tid; i < cols; i += blockDim.x) {
45
+ impl.Reduce(impl.MapIn(xr[i], 0), accum);
46
+ }
47
+ // The tree leaves the row's total in sdata[0] behind a barrier of its
48
+ // own, and the second pass needs it in every thread, so the broadcast is
49
+ // a read of that slot rather than the value handed back.
50
+ reduce_in_block(accum, sdata, tid, 1, blockDim.x, true, impl);
51
+ auto stats = impl.MapOut(sdata[0]);
52
+
53
+ for (uint64_t i = tid; i < cols; i += blockDim.x) {
54
+ yr[i] = apply(xr[i], i, stats);
55
+ }
56
+ // Nobody may still be in the pass above when the next row rewrites
57
+ // sdata. Removing this leaves every value test passing and is only
58
+ // caught by compute-sanitizer --tool racecheck.
59
+ __syncthreads();
60
+ }
61
+ }
62
+
63
+ // The second half of the split path. blockIdx.y names the row, so finding one
64
+ // costs no division, and this path is only taken where rows is small.
65
+ template <typename TypeIn, typename Stats, typename Apply>
66
+ __global__ void row_apply_kernel(
67
+ const TypeIn* x, TypeIn* y, const Stats* stats, uint64_t cols, Apply apply)
68
+ {
69
+ uint64_t row = blockIdx.y;
70
+ const TypeIn* xr = x + row * cols;
71
+ TypeIn* yr = y + row * cols;
72
+ Stats st = stats[row];
73
+
74
+ for (uint64_t i = blockIdx.x * blockDim.x + threadIdx.x; i < cols;
75
+ i += (uint64_t)blockDim.x * gridDim.x) {
76
+ yr[i] = apply(xr[i], i, st);
77
+ }
78
+ }
79
+
80
+ } // namespace cumo_detail
81
+
82
+ // Reduces each row of a contiguous rows x cols array with impl and writes the
83
+ // row back through apply. Both arrays are laid out the same way and neither may
84
+ // be the other.
85
+ template <typename TypeIn, typename Impl, typename Apply>
86
+ void cumo_row_reduce_apply(
87
+ char* px, char* py, uint64_t rows, uint64_t cols, Impl impl, Apply apply)
88
+ {
89
+ typedef decltype(impl.Identity(0)) Accum;
90
+ typedef decltype(impl.MapOut(impl.Identity(0))) Stats;
91
+ // A block that takes several rows amortizes its own launch, so the grid is
92
+ // held below the row count on purpose. This is a tuning number and not the
93
+ // ceiling for this axis, which is CUMO_MAX_GRID_DIM: a million rows of 8
94
+ // take 1286.0us at a block per row and 1177.5 here, and two million of 4
95
+ // take 2357.5 against 2094.4.
96
+ static const uint64_t max_row_blocks = 65536;
97
+ int64_t want;
98
+ unsigned int block_dim, grid_dim;
99
+ size_t shared_mem_size;
100
+
101
+ if (rows == 0 || cols == 0) {
102
+ return;
103
+ }
104
+
105
+ // A row gets one block, so a row too long for one block to walk leaves the
106
+ // rest of the device idle unless there are other rows to fill it: one row of
107
+ // a million takes 695.8us that way, against 69.6 for the nine kernels a
108
+ // layer norm otherwise costs. Past that the reduction goes through the split
109
+ // machinery, which walks the row with the whole device, and a second kernel
110
+ // applies what it found.
111
+ if (rows < (uint64_t)cumo_detail::min_grid_size &&
112
+ cols > (uint64_t)(cumo_detail::max_block_size * cumo_detail::min_reduce_per_thread)) {
113
+ cumo_na_reduction_arg_t arg;
114
+ Stats* stats = (Stats*)cumo_cuda_runtime_malloc(rows * sizeof(Stats));
115
+ // rows is below min_grid_size to be here, which is well inside the y
116
+ // limit, but that is a threshold from reduce_kernel.h and not a promise
117
+ // about this axis, so the clamp is written out rather than assumed.
118
+ dim3 apply_grid((unsigned int)cumo_get_grid_dim(cols),
119
+ (unsigned int)(rows < CUMO_MAX_GRID_DIM_Y ? rows : CUMO_MAX_GRID_DIM_Y));
120
+ unsigned int apply_block = (unsigned int)cumo_get_block_dim(cols);
121
+
122
+ memset(&arg, 0, sizeof(arg));
123
+ arg.in.ptr = px;
124
+ arg.in.step[0] = (ssize_t)(cols * sizeof(TypeIn));
125
+ arg.in.step[1] = (ssize_t)sizeof(TypeIn);
126
+ arg.in_indexer.ndim = 2;
127
+ arg.in_indexer.total_size = rows * cols;
128
+ arg.in_indexer.shape[0] = rows;
129
+ arg.in_indexer.shape[1] = cols;
130
+ arg.out.ptr = (char*)stats;
131
+ arg.out.step[0] = (ssize_t)sizeof(Stats);
132
+ arg.out_indexer.ndim = 1;
133
+ arg.out_indexer.total_size = rows;
134
+ arg.out_indexer.shape[0] = rows;
135
+
136
+ cumo_reduce_split<TypeIn, Stats, Impl>(arg, Impl(impl), (char*)stats);
137
+ cumo_detail::row_apply_kernel<TypeIn, Stats, Apply><<<apply_grid, apply_block>>>(
138
+ (const TypeIn*)px, (TypeIn*)py, stats, cols, apply);
139
+ cumo_check_launch_holding(stats);
140
+ cumo_cuda_runtime_free((char*)stats);
141
+ return;
142
+ }
143
+
144
+ // How many threads to spend on a row depends on how many rows there are to
145
+ // go around. Past enough rows to fill the device, giving each thread a run
146
+ // of the row rather than an element or two keeps the tree short: 4096 rows
147
+ // of 768 take 84.7us at a thread per element and 31.4 at sixteen. A single
148
+ // row has no other block to overlap with and wants every thread it can use,
149
+ // where the same sixteen cost 5.6us against 2.8.
150
+ if (rows < (uint64_t)cumo_detail::min_grid_size) {
151
+ want = cumo_detail::round_up_to_power_of_2((int64_t)cols);
152
+ } else {
153
+ want = cumo_detail::round_up_to_power_of_2(
154
+ ((int64_t)cols + cumo_detail::min_reduce_per_thread - 1) / cumo_detail::min_reduce_per_thread);
155
+ }
156
+ // reduce_in_block walks a power-of-two tree, and at one thread it returns
157
+ // without writing sdata at all, which would leave the broadcast reading
158
+ // whatever shared memory held. A whole warp is the smallest block here.
159
+ if (want < cumo_detail::warp_size) want = cumo_detail::warp_size;
160
+ if (want > cumo_detail::max_block_size) want = cumo_detail::max_block_size;
161
+ block_dim = (unsigned int)want;
162
+ grid_dim = (unsigned int)(rows < max_row_blocks ? rows : max_row_blocks);
163
+ shared_mem_size = block_dim * sizeof(Accum);
164
+
165
+ cumo_detail::row_reduce_apply_kernel<TypeIn, Impl, Apply><<<grid_dim, block_dim, shared_mem_size>>>(
166
+ (const TypeIn*)px, (TypeIn*)py, rows, cols, impl, apply);
167
+ cumo_cuda_runtime_check_kernel_launch();
168
+ }
169
+
170
+ #endif // CUMO_ROW_KERNEL_H
@@ -0,0 +1,48 @@
1
+ #ifndef CUMO_ROW_METHOD_H
2
+ #define CUMO_ROW_METHOD_H
3
+
4
+ #include <ruby.h>
5
+ #include "cumo/narray.h"
6
+ #include "cumo/check.h"
7
+
8
+ /* Take every pointer, then measure, then run these. Taking one runs allocate,
9
+ * which is Ruby and free to resize an operand, replace its buffer, or free one
10
+ * whose pointer was already taken, and the last of those leaves a size that
11
+ * still matches. Re-taking a pointer answers a different one only in that case.
12
+ */
13
+ #define CUMO_ROW_CHECK_READ_BUFFER(a, ptr, what) \
14
+ do { \
15
+ if (cumo_na_get_offset_pointer_for_read(a) != (ptr)) { \
16
+ rb_raise(rb_eRuntimeError, \
17
+ "the buffer of %s was replaced while it was measured", \
18
+ (what)); \
19
+ } \
20
+ } while (0)
21
+
22
+ #define CUMO_ROW_CHECK_WRITE_BUFFER(a, ptr, what) \
23
+ do { \
24
+ if (cumo_na_get_offset_pointer_for_write(a) != (ptr)) { \
25
+ rb_raise(rb_eRuntimeError, \
26
+ "the buffer of %s was replaced while it was measured", \
27
+ (what)); \
28
+ } \
29
+ } while (0)
30
+
31
+ #define CUMO_ROW_CHECK_SAME_SHAPE(nout, nin) \
32
+ do { \
33
+ cumo_narray_t *cumo_row_out = (nout); \
34
+ cumo_narray_t *cumo_row_in = (nin); \
35
+ int cumo_row_i; \
36
+ if (cumo_row_out->ndim != cumo_row_in->ndim) { \
37
+ rb_raise(cumo_na_eShapeError, \
38
+ "self or the result was reshaped while it was measured"); \
39
+ } \
40
+ for (cumo_row_i = 0; cumo_row_i < cumo_row_in->ndim; ++cumo_row_i) { \
41
+ if (cumo_row_out->shape[cumo_row_i] != cumo_row_in->shape[cumo_row_i]) { \
42
+ rb_raise(cumo_na_eShapeError, \
43
+ "self or the result was reshaped while it was measured"); \
44
+ } \
45
+ } \
46
+ } while (0)
47
+
48
+ #endif /* CUMO_ROW_METHOD_H */
@@ -176,14 +176,40 @@ cumo_get_block_dim(size_t n)
176
176
  return (block_dim == 0) ? 1 : block_dim;
177
177
  }
178
178
 
179
+ #if defined(__cplusplus)
180
+ extern "C" {
181
+ #endif
182
+
179
183
  // Raises the error a kernel launch reported, if any. Defined in cuda/runtime.c
180
184
  // because raising needs ruby.h, which the .cu translation units do not include.
181
185
  void cumo_cuda_runtime_check_kernel_launch(void);
182
186
 
187
+ // The forms to call while scratch is held, which free what they are given
188
+ // before they raise. Pass every buffer that is still outstanding, including the
189
+ // caller's: a raise here leaves through longjmp and nothing below it runs.
190
+ void cumo_cuda_runtime_check_kernel_launch_holding(char *p0, char *p1, char *p2, char *p3, char *p4);
191
+ void cumo_cuda_runtime_check_taken_status_holding(int status, char *p0, char *p1, char *p2, char *p3, char *p4);
192
+ void cumo_cuda_runtime_return_scratch(char *ptr, int wait_for_stream, cudaError_t *status);
193
+
194
+ #if defined(__cplusplus)
195
+ static inline void cumo_check_launch_holding(void *p0 = 0, void *p1 = 0, void *p2 = 0, void *p3 = 0, void *p4 = 0)
196
+ {
197
+ cumo_cuda_runtime_check_kernel_launch_holding((char*)p0, (char*)p1, (char*)p2, (char*)p3, (char*)p4);
198
+ }
199
+ static inline void cumo_check_status_holding(cudaError_t status, void *p0 = 0, void *p1 = 0, void *p2 = 0, void *p3 = 0, void *p4 = 0)
200
+ {
201
+ cumo_cuda_runtime_check_taken_status_holding((int)status, (char*)p0, (char*)p1, (char*)p2, (char*)p3, (char*)p4);
202
+ }
203
+ #endif
204
+
183
205
  // Scratch memory for a kernel that needs somewhere to put partial results.
184
206
  // Declared here rather than including cuda/memory_pool.h, which needs ruby.h.
185
207
  char* cumo_cuda_runtime_malloc(size_t size);
186
208
  void cumo_cuda_runtime_free(char *ptr);
187
209
 
210
+ #if defined(__cplusplus)
211
+ }
212
+ #endif
213
+
188
214
 
189
215
  #endif /* ifndef CUMO_TEMPLATE_KERNEL_H */
@@ -0,0 +1,82 @@
1
+ #ifndef CUMO_BF16_DEF_H
2
+ #define CUMO_BF16_DEF_H
3
+
4
+ // A struct rather than a bare uint16, so that C code which reaches for an
5
+ // arithmetic operator on an element fails to compile instead of computing on
6
+ // the bit pattern. Its layout matches __nv_bfloat16, which the kernels use.
7
+ typedef struct { unsigned short x; } cumo_bfloat;
8
+
9
+ static inline float cumo_bfloat2float(cumo_bfloat b)
10
+ {
11
+ union { unsigned int u; float f; } o;
12
+ o.u = (unsigned int)b.x << 16;
13
+ return o.f;
14
+ }
15
+
16
+ static inline cumo_bfloat cumo_float2bfloat(float f)
17
+ {
18
+ union { unsigned int u; float f; } v;
19
+ cumo_bfloat b;
20
+
21
+ v.f = f;
22
+ // bfloat16 keeps the exponent of a float, so every finite value is the top
23
+ // 16 bits rounded to nearest even. A NaN carries its payload in the low
24
+ // bits, and truncating one leaves an all-ones exponent over a zero
25
+ // mantissa, which reads as an infinity.
26
+ if ((v.u & 0x7f800000u) == 0x7f800000u && (v.u & 0x007fffffu) != 0u) {
27
+ b.x = (unsigned short)(((v.u >> 16) & 0x8000u) | 0x7fc0u);
28
+ return b;
29
+ }
30
+ b.x = (unsigned short)((v.u + 0x7fffu + ((v.u >> 16) & 1u)) >> 16);
31
+ return b;
32
+ }
33
+
34
+ static inline cumo_bfloat cumo_double2bfloat(double d)
35
+ {
36
+ union { unsigned long long u; double d; } v;
37
+ cumo_bfloat b;
38
+ unsigned long long u, sign, mant, round_bits, tie;
39
+ unsigned int shift, out;
40
+ int exp;
41
+
42
+ // Going through float would round twice, and the first rounding can land
43
+ // on a tie the second one then moves the wrong way.
44
+ v.d = d;
45
+ u = v.u;
46
+ sign = (u >> 48) & 0x8000ull;
47
+ u &= 0x7fffffffffffffffull;
48
+
49
+ if (u > 0x7ff0000000000000ull) {
50
+ b.x = (unsigned short)(sign | 0x7fc0u);
51
+ return b;
52
+ }
53
+ if (u >= 0x47f0000000000000ull) {
54
+ b.x = (unsigned short)(sign | 0x7f80u);
55
+ return b;
56
+ }
57
+ if (u < 0x3790000000000000ull) {
58
+ b.x = (unsigned short)sign;
59
+ return b;
60
+ }
61
+
62
+ exp = (int)(u >> 52) - 1023;
63
+ mant = u & 0xfffffffffffffull;
64
+ if (exp >= -126) {
65
+ out = (unsigned int)(exp + 127) << 7;
66
+ shift = 45;
67
+ } else {
68
+ mant |= 0x10000000000000ull;
69
+ shift = (unsigned int)(-81 - exp);
70
+ out = 0;
71
+ }
72
+ round_bits = mant & ((1ull << shift) - 1ull);
73
+ tie = 1ull << (shift - 1);
74
+ out += (unsigned int)(mant >> shift);
75
+ if (round_bits > tie || (round_bits == tie && ((mant >> shift) & 1ull))) {
76
+ out += 1;
77
+ }
78
+ b.x = (unsigned short)(sign | out);
79
+ return b;
80
+ }
81
+
82
+ #endif // CUMO_BF16_DEF_H
@@ -0,0 +1,28 @@
1
+ #ifndef CUMO_BF16_DEF_KERNEL_H
2
+ #define CUMO_BF16_DEF_KERNEL_H
3
+
4
+ #include <cuda_bf16.h>
5
+
6
+ typedef __nv_bfloat16 cumo_bfloat;
7
+
8
+ // The arithmetic on __nv_bfloat16 starts at sm_80, but the conversions have no
9
+ // such floor, so every operation is computed in float. For + - * / and sqrt
10
+ // that answers what the native instruction would: a float carries three times
11
+ // the mantissa bits of a bfloat16, so the second rounding cannot land on a tie
12
+ // the first one moved.
13
+ __host__ __device__ static inline float cumo_bfloat2float(cumo_bfloat b)
14
+ {
15
+ return __bfloat162float(b);
16
+ }
17
+
18
+ __host__ __device__ static inline cumo_bfloat cumo_float2bfloat(float f)
19
+ {
20
+ return __float2bfloat16(f);
21
+ }
22
+
23
+ __host__ __device__ static inline cumo_bfloat cumo_double2bfloat(double d)
24
+ {
25
+ return __double2bfloat16(d);
26
+ }
27
+
28
+ #endif // CUMO_BF16_DEF_KERNEL_H