22// SPDX-FileCopyrightText: Copyright the Vortex contributors
33
44use std:: fmt:: Debug ;
5+ use std:: ops:: Range ;
56
67use async_trait:: async_trait;
78use cudarc:: driver:: CudaFunction ;
@@ -10,14 +11,20 @@ use cudarc::driver::LaunchConfig;
1011use cudarc:: driver:: PushKernelArg ;
1112use tracing:: instrument;
1213use vortex:: array:: ArrayRef ;
14+ use vortex:: array:: ArrayVTable ;
15+ use vortex:: array:: ArrayView ;
1316use vortex:: array:: Canonical ;
1417use vortex:: array:: arrays:: PrimitiveArray ;
18+ use vortex:: array:: arrays:: Slice ;
19+ use vortex:: array:: arrays:: slice:: SliceArrayExt ;
1520use vortex:: array:: buffer:: BufferHandle ;
1621use vortex:: array:: buffer:: DeviceBufferExt ;
1722use vortex:: array:: match_each_integer_ptype;
23+ use vortex:: array:: patches:: PATCH_CHUNK_SIZE ;
1824use vortex:: dtype:: NativePType ;
1925use vortex:: encodings:: fastlanes:: BitPacked ;
2026use vortex:: encodings:: fastlanes:: BitPackedArray ;
27+ use vortex:: encodings:: fastlanes:: BitPackedArrayExt ;
2128use vortex:: encodings:: fastlanes:: BitPackedDataParts ;
2229use vortex:: encodings:: fastlanes:: unpack_iter:: BitPacked as BitPackedUnpack ;
2330use vortex:: error:: VortexResult ;
@@ -30,14 +37,71 @@ use crate::executor::CudaExecute;
3037use crate :: executor:: CudaExecutionCtx ;
3138use crate :: kernel:: patches:: build_gpu_patches;
3239use crate :: kernel:: patches:: types:: load_device_patches;
40+ use crate :: kernel:: patches:: types:: slice_device_patches;
3341
3442/// CUDA decoder for bit-packed arrays.
3543#[ derive( Debug ) ]
3644pub ( crate ) struct BitPackedExecutor ;
3745
46+ /// Build the packed buffer view for decoding `Slice(BitPacked)`.
47+ ///
48+ /// Bit-unpack kernels decode full FastLanes chunks, so the packed buffer is
49+ /// widened to chunk boundaries and `offset` is converted into the in-chunk
50+ /// starting position. The returned logical range is passed to patch
51+ /// materialization so exception metadata is sliced consistently.
52+ pub ( crate ) fn bitpacked_slice_view (
53+ bp : ArrayView < ' _ , BitPacked > ,
54+ offset : usize ,
55+ len : usize ,
56+ ) -> VortexResult < ( BufferHandle , u16 , Range < usize > ) > {
57+ let patch_range = offset..offset + len;
58+ let offset_start = patch_range. start + bp. offset ( ) as usize ;
59+ let offset_stop = offset_start + len;
60+ let bitpacked_offset = offset_start % PATCH_CHUNK_SIZE ;
61+ let block_start = offset_start - bitpacked_offset;
62+ let block_stop = offset_stop. div_ceil ( PATCH_CHUNK_SIZE ) * PATCH_CHUNK_SIZE ;
63+
64+ let encoded_start = ( block_start / 8 ) * bp. bit_width ( ) as usize ;
65+ let encoded_stop = ( block_stop / 8 ) * bp. bit_width ( ) as usize ;
66+
67+ Ok ( (
68+ bp. packed ( ) . slice ( encoded_start..encoded_stop) ,
69+ u16:: try_from ( bitpacked_offset) ?,
70+ patch_range,
71+ ) )
72+ }
73+
3874impl BitPackedExecutor {
39- fn try_specialize ( array : ArrayRef ) -> Option < BitPackedArray > {
40- array. try_downcast :: < BitPacked > ( ) . ok ( )
75+ fn try_specialize (
76+ array : ArrayRef ,
77+ ) -> VortexResult < Option < ( BitPackedArray , Option < Range < usize > > ) > > {
78+ if let Ok ( array) = array. clone ( ) . try_downcast :: < BitPacked > ( ) {
79+ return Ok ( Some ( ( array, None ) ) ) ;
80+ }
81+
82+ let Some ( slice) = array. as_opt :: < Slice > ( ) else {
83+ return Ok ( None ) ;
84+ } ;
85+ let child = slice. child ( ) ;
86+ if child. encoding_id ( ) != BitPacked . id ( ) {
87+ return Ok ( None ) ;
88+ }
89+
90+ let bp = child. as_ :: < BitPacked > ( ) ;
91+ let offset = slice. data ( ) . slice_range ( ) . start ;
92+ let len = array. len ( ) ;
93+ let ( packed, bitpacked_offset, patch_range) = bitpacked_slice_view ( bp, offset, len) ?;
94+ let sliced = BitPacked :: try_new (
95+ packed,
96+ bp. ptype ( bp. dtype ( ) ) ,
97+ child. validity ( ) ?. slice ( patch_range. clone ( ) ) ?,
98+ bp. patches ( ) ,
99+ bp. bit_width ( ) ,
100+ len,
101+ bitpacked_offset,
102+ ) ?;
103+
104+ Ok ( Some ( ( sliced, Some ( patch_range) ) ) )
41105 }
42106}
43107
@@ -49,11 +113,12 @@ impl CudaExecute for BitPackedExecutor {
49113 array : ArrayRef ,
50114 ctx : & mut CudaExecutionCtx ,
51115 ) -> VortexResult < Canonical > {
52- let array =
53- Self :: try_specialize ( array) . ok_or_else ( || vortex_err ! ( "Expected BitPackedArray" ) ) ?;
116+ let ( array, patch_range) =
117+ Self :: try_specialize ( array) ?. ok_or_else ( || vortex_err ! ( "Expected BitPackedArray" ) ) ?;
118+ let ptype = array. ptype ( array. dtype ( ) ) ;
54119
55- match_each_integer_ptype ! ( array . ptype( array . dtype ( ) ) , |A | {
56- decode_bitpacked:: <A >( array, A :: default ( ) , ctx) . await
120+ match_each_integer_ptype ! ( ptype, |A | {
121+ decode_bitpacked:: <A >( array, A :: default ( ) , patch_range , ctx) . await
57122 } )
58123 }
59124}
@@ -88,6 +153,7 @@ pub fn bitpacked_cuda_launch_config(output_width: usize, len: usize) -> VortexRe
88153pub ( crate ) async fn decode_bitpacked < A > (
89154 array : BitPackedArray ,
90155 reference : A ,
156+ patch_range : Option < Range < usize > > ,
91157 ctx : & mut CudaExecutionCtx ,
92158) -> VortexResult < Canonical >
93159where
@@ -122,7 +188,11 @@ where
122188
123189 // We hold this here to keep the device buffers alive.
124190 let device_patches = if let Some ( patches) = patches {
125- Some ( load_device_patches ( & patches, ctx) . await ?)
191+ let mut device_patches = load_device_patches ( & patches, ctx) . await ?;
192+ if let Some ( range) = patch_range {
193+ slice_device_patches ( & patches, range, & mut device_patches) ;
194+ }
195+ Some ( device_patches)
126196 } else {
127197 None
128198 } ;
@@ -551,17 +621,53 @@ mod tests {
551621 Ok ( ( ) )
552622 }
553623
624+ #[ rstest]
625+ #[ case:: direct( None , 4096 , None , 0 , 4096 * 9 / 8 ) ]
626+ #[ case:: mid_chunk( Some ( 67 ..3969 ) , 3902 , Some ( 67 ..3969 ) , 67 , 4096 * 9 / 8 ) ]
627+ #[ case:: chunk_aligned( Some ( 1024 ..3072 ) , 2048 , Some ( 1024 ..3072 ) , 0 , 2048 * 9 / 8 ) ]
628+ #[ case:: tail_chunk( Some ( 3000 ..4096 ) , 1096 , Some ( 3000 ..4096 ) , 952 , 2048 * 9 / 8 ) ]
629+ #[ crate :: test]
630+ fn test_bitunpack_try_specialize_slices (
631+ #[ case] range : Option < Range < usize > > ,
632+ #[ case] expected_len : usize ,
633+ #[ case] expected_patch_range : Option < Range < usize > > ,
634+ #[ case] expected_offset : u16 ,
635+ #[ case] expected_packed_len : usize ,
636+ ) -> VortexResult < ( ) > {
637+ let values = PrimitiveArray :: new (
638+ ( 0u16 ..4096 )
639+ . map ( |i| if i % 1000 == 0 { 600 } else { i % 512 } )
640+ . collect :: < Buffer < _ > > ( ) ,
641+ NonNullable ,
642+ ) ;
643+ let bitpacked = BitPacked :: encode (
644+ & values. into_array ( ) ,
645+ 9 ,
646+ & mut LEGACY_SESSION . create_execution_ctx ( ) ,
647+ ) ?;
648+ assert ! ( bitpacked. patches( ) . is_some( ) ) ;
649+ let array = if let Some ( range) = range {
650+ bitpacked. into_array ( ) . slice ( range) ?
651+ } else {
652+ bitpacked. into_array ( )
653+ } ;
654+
655+ let ( specialized, patch_range) =
656+ BitPackedExecutor :: try_specialize ( array) ?. vortex_expect ( "expected BitPacked input" ) ;
657+
658+ assert_eq ! ( specialized. len( ) , expected_len) ;
659+ assert_eq ! ( specialized. offset( ) , expected_offset) ;
660+ assert_eq ! ( specialized. packed( ) . len( ) , expected_packed_len) ;
661+ assert_eq ! ( patch_range, expected_patch_range) ;
662+
663+ Ok ( ( ) )
664+ }
665+
554666 /// Test slicing a bitpacked array with patches where the slice boundary
555667 /// falls in the middle of a chunk's patch range, creating a non-zero
556668 /// offset_within_chunk.
557669 #[ crate :: test]
558670 fn test_cuda_bitunpack_sliced_patches_offset_within_chunk ( ) -> VortexResult < ( ) > {
559- // TODO(#7839): BitPacked SliceReduce returns None when patches are present,
560- // producing SliceArray instead of BitPacked. CUDA cannot handle this yet.
561- if true {
562- return Ok ( ( ) ) ;
563- }
564-
565671 let mut cuda_ctx = CudaSession :: create_execution_ctx ( & VortexSession :: empty ( ) )
566672 . vortex_expect ( "failed to create execution context" ) ;
567673
@@ -604,12 +710,6 @@ mod tests {
604710 /// Test slicing a bitpacked array multiple times, accumulating offset_within_chunk.
605711 #[ crate :: test]
606712 fn test_cuda_bitunpack_double_sliced_patches ( ) -> VortexResult < ( ) > {
607- // TODO(#7839): BitPacked SliceReduce returns None when patches are present,
608- // producing SliceArray instead of BitPacked. CUDA cannot handle this yet.
609- if true {
610- return Ok ( ( ) ) ;
611- }
612-
613713 let mut cuda_ctx = CudaSession :: create_execution_ctx ( & VortexSession :: empty ( ) )
614714 . vortex_expect ( "failed to create execution context" ) ;
615715
@@ -664,12 +764,6 @@ mod tests {
664764 /// Test slicing to skip an entire chunk's worth of patches.
665765 #[ crate :: test]
666766 fn test_cuda_bitunpack_sliced_skip_first_chunk_patches ( ) -> VortexResult < ( ) > {
667- // TODO(#7839): BitPacked SliceReduce returns None when patches are present,
668- // producing SliceArray instead of BitPacked. CUDA cannot handle this yet.
669- if true {
670- return Ok ( ( ) ) ;
671- }
672-
673767 let mut cuda_ctx = CudaSession :: create_execution_ctx ( & VortexSession :: empty ( ) )
674768 . vortex_expect ( "failed to create execution context" ) ;
675769
0 commit comments