Repository navigation
[CUB] Make ReduceByKey run-to-run deterministic #11032
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
19d1dfb
bb6f846
009b23f
a7af399
892e0e4
83828fa
7fc9cdf
a799dd2
de9e944
35780b3
d3dd101
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -46,6 +46,7 @@ | |||||
| #include <cuda/__memory_resource/get_memory_resource.h> | ||||||
| #include <cuda/__stream/get_stream.h> | ||||||
| #include <cuda/__stream/stream_ref.h> | ||||||
| #include <cuda/__type_traits/is_floating_point.h> | ||||||
| #include <cuda/argument> | ||||||
| #include <cuda/std/__execution/env.h> | ||||||
| #include <cuda/std/__functional/identity.h> | ||||||
|
|
@@ -153,7 +154,9 @@ inline constexpr bool is_non_deterministic_v = | |||||
| //! ==================================== | ||||||
| //! | ||||||
| //! ``cub::DeviceReduce`` supports all three :ref:`determinism guarantees <cccl-determinism>`; the | ||||||
| //! default is ``run_to_run``. | ||||||
| //! default is ``run_to_run``. ``ReduceByKey`` has separate type/operator constraints, documented on its | ||||||
| //! environment overload below. The implementation details below apply to the other reductions; | ||||||
| //! ``ReduceByKey`` does not use RFA or atomic accumulation. | ||||||
| //! | ||||||
| //! - ``run_to_run`` (the default) is reproducible because, for a given GPU, every launch with the same input, | ||||||
| //! build, and launch configuration selects the *same* tuning policy and therefore performs the *same* fixed | ||||||
|
|
@@ -2734,15 +2737,26 @@ public: | |||||
| //! respectively. The total number of runs encountered is written to ``d_num_runs_out``. | ||||||
| //! | ||||||
| //! - The ``==`` equality operator is used to determine whether keys are equivalent | ||||||
| //! - Provides "run-to-run" determinism for pseudo-associative reduction | ||||||
| //! (e.g., addition of floating point types) on the same GPU device. | ||||||
| //! - Requests "run-to-run" determinism by default. It is supported for integral types with known operators, | ||||||
| //! primitive types with min/max, and floating-point types with ``cuda::std::plus``. | ||||||
| //! - Pass ``cuda::execution::require(cuda::execution::determinism::not_guaranteed)`` in the environment to opt out. | ||||||
| //! This removes the determinism requirement without changing the algorithm's other requirements. | ||||||
| //! - "gpu-to-gpu" determinism is supported for integral types with known operators and primitive types with min/max. | ||||||
| //! Floating-point addition is unsupported; ``ReduceByKey`` does not use RFA. | ||||||
| //! Other combinations under "run-to-run" or "gpu-to-gpu" are rejected at compile time. | ||||||
|
pauleonix marked this conversation as resolved.
Resolved
|
||||||
| //! - Let ``out`` be any of | ||||||
| //! ``[d_unique_out, d_unique_out + *d_num_runs_out)`` | ||||||
| //! ``[d_aggregates_out, d_aggregates_out + *d_num_runs_out)`` | ||||||
| //! ``d_num_runs_out``. The ranges represented by ``out`` shall not overlap | ||||||
| //! ``[d_keys_in, d_keys_in + num_items)``, | ||||||
| //! ``[d_values_in, d_values_in + num_items)`` nor ``out`` in any way. | ||||||
| //! | ||||||
| //! These determinism constraints apply to this environment overload and its accumulator type. Known CUDA binary | ||||||
| //! operators are ``cuda::minimum``, ``cuda::maximum``, and ``cuda::std::plus``, ``multiplies``, ``bit_and``, | ||||||
| //! ``bit_or``, ``bit_xor``, ``logical_and``, and ``logical_or``. Primitive accumulators are the types recognized by | ||||||
| //! ``cub::Traits``; floating-point accumulators (including CUDA extended types) are recognized by | ||||||
| //! ``cuda::is_floating_point_v``. The operator must support the accumulator type. | ||||||
| //! | ||||||
| //! Snippet | ||||||
| //! +++++++++++++++++++++++++++++++++++++++++++++ | ||||||
| //! | ||||||
|
|
@@ -2780,7 +2794,8 @@ public: | |||||
| //! | ||||||
| //! @tparam EnvT | ||||||
| //! **[inferred]** Execution environment type. Default is ``cuda::std::execution::env<>``. | ||||||
| //! Supports customization of stream via ``cuda::get_stream``. | ||||||
| //! Supports customization of stream via ``cuda::get_stream`` and determinism through | ||||||
| //! ``cuda::execution::require``. | ||||||
| //! | ||||||
| //! @param[in] d_keys_in | ||||||
| //! Pointer to the input sequence of keys | ||||||
|
|
@@ -2830,15 +2845,43 @@ public: | |||||
| { | ||||||
| _CCCL_NVTX_RANGE_SCOPE("cub::DeviceReduce::ReduceByKey"); | ||||||
|
|
||||||
| using OffsetT = detail::choose_offset_t<NumItemsT>; | ||||||
| using EqualityOp = ::cuda::std::equal_to<>; | ||||||
| using default_policy_selector = detail::reduce_by_key::policy_selector_from_types< | ||||||
| ReductionOpT, | ||||||
| ::cuda::std::__accumulator_t<ReductionOpT, detail::it_value_t<ValuesInputIteratorT>>, | ||||||
| detail::non_void_value_t<UniqueOutputIteratorT, detail::it_value_t<KeysInputIteratorT>>>; | ||||||
| static_assert(!::cuda::std::execution::__queryable_with<EnvT, ::cuda::execution::determinism::__get_determinism_t>, | ||||||
| "Determinism should be used inside requires to have an effect."); | ||||||
|
|
||||||
| using requirements_t = ::cuda::std::execution:: | ||||||
| __query_result_or_t<EnvT, ::cuda::execution::__get_requirements_t, ::cuda::std::execution::env<>>; | ||||||
| using requested_determinism_t = | ||||||
| ::cuda::std::execution::__query_result_or_t<requirements_t, | ||||||
| ::cuda::execution::determinism::__get_determinism_t, | ||||||
| ::cuda::execution::determinism::run_to_run_t>; | ||||||
| using accum_t = ::cuda::std::__accumulator_t<ReductionOpT, detail::it_value_t<ValuesInputIteratorT>>; | ||||||
| using key_t = detail::non_void_value_t<UniqueOutputIteratorT, detail::it_value_t<KeysInputIteratorT>>; | ||||||
| using OffsetT = detail::choose_offset_t<NumItemsT>; | ||||||
| using EqualityOp = ::cuda::std::equal_to<>; | ||||||
|
|
||||||
| constexpr bool is_run_to_run_required = | ||||||
| ::cuda::std::is_same_v<requested_determinism_t, ::cuda::execution::determinism::run_to_run_t>; | ||||||
| constexpr bool is_gpu_to_gpu_required = | ||||||
| ::cuda::std::is_same_v<requested_determinism_t, ::cuda::execution::determinism::gpu_to_gpu_t>; | ||||||
| constexpr bool is_safe_integral_op = | ||||||
| ::cuda::std::is_integral_v<accum_t> && detail::is_cuda_binary_operator<ReductionOpT>; | ||||||
| constexpr bool is_primitive_min_max_op = | ||||||
| detail::is_primitive_v<accum_t> && detail::is_cuda_minimum_maximum_v<ReductionOpT, accum_t>; | ||||||
| constexpr bool is_fp_plus_op = | ||||||
| ::cuda::is_floating_point_v<accum_t> && detail::is_cuda_std_plus_v<ReductionOpT, accum_t>; | ||||||
|
|
||||||
| static_assert(!is_run_to_run_required || is_safe_integral_op || is_primitive_min_max_op || is_fp_plus_op, | ||||||
| "run_to_run deterministic reduce-by-key requires integral types with known operators, " | ||||||
| "primitive types with min/max, or floating-point types with plus operator"); | ||||||
| static_assert(!is_gpu_to_gpu_required || is_safe_integral_op || is_primitive_min_max_op, | ||||||
| "gpu_to_gpu deterministic reduce-by-key requires integral types with known operators or " | ||||||
| "primitive types with min/max"); | ||||||
|
|
||||||
| static constexpr bool stable_reduction_order = is_run_to_run_required && is_fp_plus_op; | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
No need to make it static (and below as well) |
||||||
| using default_policy_selector = detail::reduce_by_key::policy_selector_from_types<ReductionOpT, accum_t, key_t>; | ||||||
| return detail::dispatch_with_env_and_tuning<default_policy_selector>( | ||||||
| env, [&](auto policy_selector, void* storage, size_t& bytes, cudaStream_t stream) { | ||||||
| return detail::reduce_by_key::dispatch( | ||||||
| return detail::reduce_by_key::dispatch<stable_reduction_order>( | ||||||
| storage, | ||||||
| bytes, | ||||||
| d_keys_in, | ||||||
|
|
@@ -2867,8 +2910,7 @@ public: | |||||
| //! respectively. The total number of runs encountered is written to ``d_num_runs_out``. | ||||||
| //! | ||||||
| //! - The ``==`` equality operator is used to determine whether keys are equivalent | ||||||
| //! - Provides "run-to-run" determinism for pseudo-associative reduction | ||||||
| //! (e.g., addition of floating point types) on the same GPU device. | ||||||
| //! - Provides "run-to-run" determinism for floating-point reduction with ``cuda::std::plus`` on the same GPU device. | ||||||
| //! However, results for pseudo-associative reduction may be inconsistent | ||||||
| //! from one device to a another device of a different compute-capability | ||||||
| //! because CUB can employ different tile-sizing for different architectures. | ||||||
|
|
@@ -3011,10 +3053,14 @@ public: | |||||
| { | ||||||
| _CCCL_NVTX_RANGE_SCOPE_IF(d_temp_storage, "cub::DeviceReduce::ReduceByKey"); | ||||||
|
|
||||||
| using accum_t = ::cuda::std::__accumulator_t<ReductionOpT, detail::it_value_t<ValuesInputIteratorT>>; | ||||||
| using OffsetT = detail::choose_offset_t<NumItemsT>; | ||||||
| using EqualityOp = ::cuda::std::equal_to<>; | ||||||
|
|
||||||
| return detail::reduce_by_key::dispatch( | ||||||
| static constexpr bool stable_reduction_order = | ||||||
| ::cuda::is_floating_point_v<accum_t> && detail::is_cuda_std_plus_v<ReductionOpT, accum_t>; | ||||||
|
|
||||||
| return detail::reduce_by_key::dispatch<stable_reduction_order>( | ||||||
| d_temp_storage, | ||||||
| temp_storage_bytes, | ||||||
| d_keys_in, | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -112,6 +112,14 @@ struct streaming_context | |
| } | ||
| }; | ||
|
|
||
| // Bind the stability flag while selecting the default and fallback agent policies. | ||
| template <bool StableReductionOrder, typename Policy, typename... Args> | ||
| using vsmem_helper_t = | ||
| vsmem_helper_with_fallback_impl<Policy, | ||
| AgentReduceByKey<Policy, StableReductionOrder, Args...>, | ||
| policy_wrapper_t<Policy, 64, 1>, | ||
| AgentReduceByKey<policy_wrapper_t<Policy, 64, 1>, StableReductionOrder, Args...>>; | ||
|
|
||
| /** | ||
| * @brief Multi-block reduce-by-key sweep kernel entry point | ||
| * | ||
|
|
@@ -145,6 +153,9 @@ struct streaming_context | |
| * @tparam OffsetT | ||
| * Signed integer type for global offsets | ||
| * | ||
| * @tparam StableReductionOrder | ||
| * Whether to use a stable reduction order across tiles | ||
| * | ||
| * @param d_keys_in | ||
| * Pointer to the input sequence of keys | ||
| * | ||
|
|
@@ -187,7 +198,8 @@ template <typename PolicySelector, | |
| typename ReductionOpT, | ||
| typename OffsetT, | ||
| typename AccumT, | ||
| typename StreamingContextT> | ||
| typename StreamingContextT, | ||
| bool StableReductionOrder> | ||
| #if _CCCL_HAS_CONCEPTS() | ||
| requires reduce_by_key_policy_selector<PolicySelector> | ||
| #endif | ||
|
|
@@ -217,9 +229,9 @@ __launch_bounds__(int(current_policy<PolicySelector>().lookback.threads_per_bloc | |
| policy.lookback.lookback_delay.delay, | ||
| policy.lookback.lookback_delay.l2_write_latency>>; | ||
|
|
||
| using vsmem_helper_t = vsmem_helper_default_fallback_policy_t< | ||
| using vsmem_helper_t = reduce_by_key::vsmem_helper_t< | ||
| StableReductionOrder, | ||
| AgentReduceByKeyPolicyT, | ||
| AgentReduceByKey, | ||
| KeysInputIteratorT, | ||
| UniqueOutputIteratorT, | ||
| ValuesInputIteratorT, | ||
|
|
@@ -374,9 +386,9 @@ struct CCCL_DEPRECATED_BECAUSE("Use the tuning API for DeviceReduce::ReduceByKey | |
| CUB_RUNTIME_FUNCTION _CCCL_VISIBILITY_HIDDEN _CCCL_FORCEINLINE cudaError_t | ||
| Invoke(ScanInitKernelT init_kernel, ReduceByKeyKernelT reduce_by_key_kernel) | ||
| { | ||
| using vsmem_helper_t = detail::vsmem_helper_default_fallback_policy_t< | ||
| using vsmem_helper_t = detail::reduce_by_key::vsmem_helper_t< | ||
| false, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Q: why are we passing
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is the deprecated dispatcher, which has no
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @ahmedtaha100 please use an inline comment to indicate the name of the template parameter |
||
| typename ActivePolicyT::ReduceByKeyPolicyT, | ||
| detail::reduce_by_key::AgentReduceByKey, | ||
| KeysInputIteratorT, | ||
| UniqueOutputIteratorT, | ||
| ValuesInputIteratorT, | ||
|
|
@@ -555,7 +567,8 @@ struct CCCL_DEPRECATED_BECAUSE("Use the tuning API for DeviceReduce::ReduceByKey | |
| ReductionOpT, | ||
| OffsetT, | ||
| AccumT, | ||
| streaming_context_t>); | ||
| streaming_context_t, | ||
| false>); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here |
||
| } | ||
|
|
||
| /** | ||
|
|
@@ -650,7 +663,7 @@ struct CCCL_DEPRECATED_BECAUSE("Use the tuning API for DeviceReduce::ReduceByKey | |
| namespace detail::reduce_by_key | ||
| { | ||
| // we move the conversion of the policy to the agent policy and its use out of the lambda below, so MSVC does not ICE | ||
| template <typename PolicyGetter, typename... Args> | ||
| template <bool StableReductionOrder, typename PolicyGetter, typename... Args> | ||
| _CCCL_HOST_DEVICE_API auto determine_threads_items_vsmem(PolicyGetter policy_getter) | ||
| { | ||
| // TODO(bgruber): refactor this in the future | ||
|
|
@@ -664,13 +677,14 @@ _CCCL_HOST_DEVICE_API auto determine_threads_items_vsmem(PolicyGetter policy_get | |
| delay_constructor_t<policy.lookback.lookback_delay.kind, | ||
| policy.lookback.lookback_delay.delay, | ||
| policy.lookback.lookback_delay.l2_write_latency>>; | ||
| using vsmem_helper_t = vsmem_helper_default_fallback_policy_t<Policy, AgentReduceByKey, Args...>; | ||
| using vsmem_helper_t = reduce_by_key::vsmem_helper_t<StableReductionOrder, Policy, Args...>; | ||
| return ::cuda::std::tuple{vsmem_helper_t::agent_policy_t::BLOCK_THREADS, | ||
| vsmem_helper_t::agent_policy_t::ITEMS_PER_THREAD, | ||
| vsmem_helper_t::vsmem_per_block}; | ||
| } | ||
|
|
||
| template <typename KeysInputIteratorT, | ||
| template <bool StableReductionOrder, | ||
| typename KeysInputIteratorT, | ||
| typename UniqueOutputIteratorT, | ||
| typename ValuesInputIteratorT, | ||
| typename AggregatesOutputIteratorT, | ||
|
|
@@ -721,6 +735,7 @@ CUB_RUNTIME_FUNCTION _CCCL_FORCEINLINE static cudaError_t dispatch( | |
| #endif // _CCCL_HOSTED() && defined(CUB_DEBUG_LOG) | ||
|
|
||
| const auto [threads_per_block, items_per_thread, vsmem_per_block] = determine_threads_items_vsmem< | ||
| StableReductionOrder, | ||
| decltype(policy_getter), | ||
| KeysInputIteratorT, | ||
| UniqueOutputIteratorT, | ||
|
|
@@ -799,7 +814,8 @@ CUB_RUNTIME_FUNCTION _CCCL_FORCEINLINE static cudaError_t dispatch( | |
| ReductionOpT, | ||
| OffsetT, | ||
| AccumT, | ||
| streaming_context_t>; | ||
| streaming_context_t, | ||
| StableReductionOrder>; | ||
|
|
||
| int reduce_by_key_sm_occupancy{}; | ||
| if (const auto error = | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: let's please keep the ordering of the template parameters consistent, so this should go at the end of the template parameter list