Skip to content

Commit f6f1ccf

Browse files
committed
CUDA execute Patched
Signed-off-by: Andrew Duffy <andrew@a10y.dev>
1 parent 44c511d commit f6f1ccf

9 files changed

Lines changed: 646 additions & 17 deletions

File tree

‎vortex-array/public-api.lock‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3368,6 +3368,10 @@ pub struct vortex_array::arrays::patched::Patched
33683368

33693369
impl vortex_array::arrays::patched::Patched
33703370

3371+
pub const vortex_array::arrays::patched::Patched::ID: vortex_array::ArrayId
3372+
3373+
impl vortex_array::arrays::patched::Patched
3374+
33713375
pub fn vortex_array::arrays::patched::Patched::from_array_and_patches(inner: vortex_array::ArrayRef, patches: &vortex_array::patches::Patches, ctx: &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult<vortex_array::Array<vortex_array::arrays::patched::Patched>>
33723376

33733377
impl core::clone::Clone for vortex_array::arrays::patched::Patched
@@ -3584,6 +3588,8 @@ pub fn T::patch_values(&self) -> &vortex_array::ArrayRef
35843588

35853589
pub fn T::slots_view(&self) -> vortex_array::arrays::patched::PatchedSlotsView<'_>
35863590

3591+
pub fn vortex_array::arrays::patched::apply_patches_primitive<V: vortex_array::dtype::NativePType>(output: &mut [V], offset: usize, len: usize, n_lanes: usize, lane_offsets: &[u32], indices: &[u16], values: &[V])
3592+
35873593
pub type vortex_array::arrays::patched::PatchedArray = vortex_array::Array<vortex_array::arrays::patched::Patched>
35883594

35893595
pub mod vortex_array::arrays::primitive
@@ -6218,6 +6224,10 @@ pub struct vortex_array::arrays::Patched
62186224

62196225
impl vortex_array::arrays::patched::Patched
62206226

6227+
pub const vortex_array::arrays::patched::Patched::ID: vortex_array::ArrayId
6228+
6229+
impl vortex_array::arrays::patched::Patched
6230+
62216231
pub fn vortex_array::arrays::patched::Patched::from_array_and_patches(inner: vortex_array::ArrayRef, patches: &vortex_array::patches::Patches, ctx: &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult<vortex_array::Array<vortex_array::arrays::patched::Patched>>
62226232

62236233
impl core::clone::Clone for vortex_array::arrays::patched::Patched

‎vortex-array/src/arrays/patched/vtable/mod.rs‎

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@ pub type PatchedArray = Array<Patched>;
5757
#[derive(Clone, Debug)]
5858
pub struct Patched;
5959

60+
impl Patched {
61+
/// The array ID for Patched arrays.
62+
pub const ID: ArrayId = ArrayId::new_ref("vortex.patched");
63+
}
64+
6065
impl ValidityChild<Patched> for Patched {
6166
fn validity_child(array: ArrayView<'_, Patched>) -> ArrayRef {
6267
array.inner().clone()
@@ -99,7 +104,7 @@ impl VTable for Patched {
99104
type ValidityVTable = ValidityVTableFromChild;
100105

101106
fn id(&self) -> ArrayId {
102-
ArrayId::new_ref("vortex.patched")
107+
Self::ID
103108
}
104109

105110
fn validate(
@@ -318,7 +323,12 @@ impl VTable for Patched {
318323
}
319324

320325
/// Apply patches on top of the existing value types.
321-
fn apply_patches_primitive<V: NativePType>(
326+
///
327+
/// This function is used to overwrite values in the output buffer with patch values
328+
/// at the specified indices. It handles the chunked layout where patches are organized
329+
/// by lanes within 1024-element chunks.
330+
#[allow(clippy::too_many_arguments)]
331+
pub fn apply_patches_primitive<V: NativePType>(
322332
output: &mut [V],
323333
offset: usize,
324334
len: usize,

‎vortex-cuda/benches/bitpacked_cuda.rs‎

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,17 @@ use criterion::Criterion;
1919
use criterion::Throughput;
2020
use cudarc::driver::DeviceRepr;
2121
use futures::executor::block_on;
22+
use vortex::array::ArrayRef;
2223
use vortex::array::IntoArray;
24+
use vortex::array::VortexSessionExecute;
25+
use vortex::array::arrays::Patched;
2326
use vortex::array::arrays::PrimitiveArray;
2427
use vortex::array::validity::Validity::NonNullable;
2528
use vortex::buffer::Buffer;
2629
use vortex::dtype::NativePType;
30+
use vortex::encodings::fastlanes::BitPacked as BitPackedEncoding;
2731
use vortex::encodings::fastlanes::BitPackedArray;
32+
use vortex::encodings::fastlanes::BitPackedArrayExt;
2833
use vortex::encodings::fastlanes::BitPackedData;
2934
use vortex::encodings::fastlanes::unpack_iter::BitPacked;
3035
use vortex::error::VortexExpect;
@@ -39,8 +44,7 @@ use crate::common::TimedLaunchStrategy;
3944
const N_ROWS: usize = 100_000_000;
4045

4146
/// Patch frequencies to benchmark (as fractions)
42-
const PATCH_FREQUENCIES: &[(f64, &str)] =
43-
&[(0.001, "0.1%"), (0.01, "1%"), (0.05, "5%"), (0.10, "10%")];
47+
const PATCH_FREQUENCIES: &[(f64, &str)] = &[(0.01, "1%"), (0.10, "10%")];
4448

4549
/// Create a bit-packed array with the given bit width
4650
fn make_bitpacked_array<T>(bit_width: u8, len: usize) -> BitPackedArray
@@ -61,14 +65,20 @@ where
6165
.vortex_expect("failed to create BitPacked array")
6266
}
6367

64-
/// Create a bit-packed array with the given bit width and patch frequency.
68+
/// Create a `Patched` array wrapping a patch-free `BitPacked` array with the given
69+
/// bit width and patch frequency.
6570
///
6671
/// `patch_frequency` is a fraction (0.0 to 1.0) indicating what proportion of values
6772
/// should exceed the bit width and become patches.
6873
///
6974
/// This function uses bit_width=6 internally since patch values need to exceed
7075
/// the bit width but still fit in u8 for the From<u8> trait bound.
71-
fn make_bitpacked_array_with_patches<T>(len: usize, patch_frequency: f64) -> BitPackedArray
76+
///
77+
/// The bit-packed array initially produced by `BitPackedData::encode` carries its
78+
/// patches inline. We extract those patches and re-wrap the patch-free `BitPacked`
79+
/// in a `Patched` so the CUDA executor dispatches through `PatchedExecutor` (which
80+
/// fuses patching with bit-unpacking).
81+
fn make_bitpacked_array_with_patches<T>(len: usize, patch_frequency: f64) -> ArrayRef
7282
where
7383
T: NativePType + Add<Output = T> + From<u8>,
7484
{
@@ -97,8 +107,33 @@ where
97107
.collect();
98108

99109
let primitive_array = PrimitiveArray::new(Buffer::from(values), NonNullable).into_array();
100-
BitPackedData::encode(&primitive_array, bit_width)
101-
.vortex_expect("failed to create BitPacked array with patches")
110+
let bitpacked = BitPackedData::encode(&primitive_array, bit_width)
111+
.vortex_expect("failed to create BitPacked array with patches");
112+
113+
let patches = bitpacked
114+
.patches()
115+
.vortex_expect("expected patches at non-zero patch frequency");
116+
117+
let bitpacked_without_patches = BitPackedEncoding::try_new(
118+
bitpacked.packed().clone(),
119+
bitpacked.dtype().as_ptype(),
120+
BitPackedArrayExt::validity(&bitpacked),
121+
None,
122+
bitpacked.bit_width(),
123+
bitpacked.len(),
124+
bitpacked.offset(),
125+
)
126+
.vortex_expect("failed to rebuild patch-free BitPacked")
127+
.into_array();
128+
129+
let session = VortexSession::empty();
130+
Patched::from_array_and_patches(
131+
bitpacked_without_patches,
132+
&patches,
133+
&mut session.create_execution_ctx(),
134+
)
135+
.vortex_expect("failed to wrap BitPacked in Patched")
136+
.into_array()
102137
}
103138

104139
/// Generic benchmark function for a specific type and bit width

‎vortex-cuda/kernels/src/patched.cu‎

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
// SPDX-FileCopyrightText: Copyright the Vortex contributors
3+
4+
#include "types.cuh"
5+
6+
/// Apply patches to an output array using the transposed Patched array format.
7+
///
8+
/// This kernel uses a thread-per-lane model where each thread is assigned to
9+
/// one (chunk, lane) slot and applies all patches in that slot.
10+
template <typename ValueT>
11+
__device__ void patched(ValueT *const output,
12+
const uint32_t *const lane_offsets,
13+
const uint16_t *const patch_indices,
14+
const ValueT *const patch_values,
15+
uint32_t n_lanes,
16+
uint32_t total_lane_slots,
17+
uint64_t offset,
18+
uint64_t len) {
19+
const uint32_t lane_slot = blockIdx.x * blockDim.x + threadIdx.x;
20+
21+
// Early return if this thread is beyond the number of lane slots
22+
if (lane_slot >= total_lane_slots) {
23+
return;
24+
}
25+
26+
// Determine which chunk this lane slot belongs to
27+
const uint32_t chunk = lane_slot / n_lanes;
28+
29+
// Get the range of patches for this lane slot
30+
const uint32_t start = lane_offsets[lane_slot];
31+
const uint32_t stop = lane_offsets[lane_slot + 1];
32+
33+
// Apply all patches in this lane
34+
for (uint32_t p = start; p < stop; p++) {
35+
// Get within-chunk index and compute global position
36+
const uint16_t within_chunk_idx = patch_indices[p];
37+
const uint64_t global_idx = static_cast<uint64_t>(chunk) * 1024 + within_chunk_idx;
38+
39+
// Check bounds (for sliced arrays)
40+
if (global_idx < offset) {
41+
continue;
42+
}
43+
44+
if (global_idx >= offset + len) {
45+
break;
46+
}
47+
48+
output[global_idx - offset] = patch_values[p];
49+
}
50+
}
51+
52+
#define GENERATE_PATCHED_KERNEL(value_suffix, ValueT) \
53+
extern "C" __global__ void patched_##value_suffix(ValueT *const output, \
54+
const uint32_t *const lane_offsets, \
55+
const uint16_t *const patch_indices, \
56+
const ValueT *const patch_values, \
57+
uint32_t n_lanes, \
58+
uint32_t total_lane_slots, \
59+
uint64_t offset, \
60+
uint64_t len) { \
61+
patched(output, lane_offsets, patch_indices, patch_values, n_lanes, total_lane_slots, offset, len); \
62+
}
63+
64+
// Generate for all native SIMD ptypes
65+
FOR_EACH_NATIVE_SIMD_PTYPE(GENERATE_PATCHED_KERNEL)

‎vortex-cuda/src/kernel/encodings/bitpacked.rs‎

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,14 @@ use vortex::encodings::fastlanes::unpack_iter::BitPacked as BitPackedUnpack;
2323
use vortex::error::VortexResult;
2424
use vortex::error::vortex_ensure;
2525
use vortex::error::vortex_err;
26+
use vortex_error::vortex_bail;
2627

2728
use crate::CudaBufferExt;
2829
use crate::CudaDeviceBuffer;
2930
use crate::executor::CudaExecute;
3031
use crate::executor::CudaExecutionCtx;
3132
use crate::kernel::patches::gpu::GPUPatches;
33+
use crate::kernel::patches::types::DevicePatches;
3234
use crate::kernel::patches::types::transpose_patches;
3335

3436
/// CUDA decoder for bit-packed arrays.
@@ -53,7 +55,7 @@ impl CudaExecute for BitPackedExecutor {
5355
Self::try_specialize(array).ok_or_else(|| vortex_err!("Expected BitPackedArray"))?;
5456

5557
match_each_integer_ptype!(array.ptype(array.dtype()), |A| {
56-
decode_bitpacked::<A>(array, A::default(), ctx).await
58+
decode_bitpacked::<A>(array, A::default(), None, ctx).await
5759
})
5860
}
5961
}
@@ -90,6 +92,7 @@ unsafe impl DeviceRepr for GPUPatches {}
9092
pub(crate) async fn decode_bitpacked<A>(
9193
array: BitPackedArray,
9294
reference: A,
95+
device_patches: Option<DevicePatches>,
9396
ctx: &mut CudaExecutionCtx,
9497
) -> VortexResult<Canonical>
9598
where
@@ -101,7 +104,7 @@ where
101104
bit_width,
102105
len,
103106
packed,
104-
patches,
107+
patches: interior_patches,
105108
validity,
106109
} = BitPacked::into_parts(array);
107110

@@ -122,11 +125,14 @@ where
122125
let cuda_function = bitpacked_cuda_kernel(bit_width, output_width, ctx)?;
123126
let config = bitpacked_cuda_launch_config(output_width, len)?;
124127

125-
// We hold this here to keep the device buffers alive.
126-
let device_patches = if let Some(patches) = patches {
127-
Some(transpose_patches(&patches, ctx).await?)
128-
} else {
129-
None
128+
// Execute the patch kind to get device patches
129+
let device_patches = match (interior_patches, device_patches) {
130+
(None, None) => None,
131+
(Some(patches), None) => Some(transpose_patches(&patches, ctx).await?),
132+
(None, Some(device_patches)) => Some(device_patches),
133+
(Some(_), Some(_)) => {
134+
vortex_bail!("Cannot execute bitpacked array with interior and exterior patches")
135+
}
130136
};
131137

132138
let patches_arg = if let Some(p) = &device_patches {

‎vortex-cuda/src/kernel/encodings/for_.rs‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ impl CudaExecute for FoRExecutor {
5656
if let Some(bitpacked) = array.encoded().as_opt::<BitPacked>() {
5757
match_each_integer_ptype!(bitpacked.ptype(bitpacked.dtype()), |P| {
5858
let reference: P = array.reference_scalar().try_into()?;
59-
return decode_bitpacked(bitpacked.into_owned(), reference, ctx).await;
59+
return decode_bitpacked(bitpacked.into_owned(), reference, None, ctx).await;
6060
})
6161
}
6262

@@ -67,7 +67,7 @@ impl CudaExecute for FoRExecutor {
6767
let slice_range = slice_array.slice_range().clone();
6868
let unpacked = match_each_integer_ptype!(bitpacked.ptype(bitpacked.dtype()), |P| {
6969
let reference: P = array.reference_scalar().try_into()?;
70-
decode_bitpacked(bitpacked.into_owned(), reference, ctx).await?
70+
decode_bitpacked(bitpacked.into_owned(), reference, None, ctx).await?
7171
});
7272

7373
return unpacked

‎vortex-cuda/src/kernel/encodings/mod.rs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ mod bitpacked;
66
mod date_time_parts;
77
mod decimal_byte_parts;
88
mod for_;
9+
mod patched;
910
mod runend;
1011
mod sequence;
1112
mod zigzag;
@@ -18,6 +19,7 @@ pub(crate) use bitpacked::BitPackedExecutor;
1819
pub(crate) use date_time_parts::DateTimePartsExecutor;
1920
pub(crate) use decimal_byte_parts::DecimalBytePartsExecutor;
2021
pub(crate) use for_::FoRExecutor;
22+
pub(crate) use patched::PatchedExecutor;
2123
pub(crate) use runend::RunEndExecutor;
2224
pub(crate) use sequence::SequenceExecutor;
2325
pub(crate) use zigzag::ZigZagExecutor;

0 commit comments

Comments
 (0)