Custom Policies#

The dynamic selection API is an experimental feature in the oneAPI DPC++ Library (oneDPL) that selects an execution resource based on a chosen selection policy. While several policies are provided out of the box, you can create custom policies to implement application-specific selection strategies.

Using policy_base#

The recommended approach for creating custom policies is to inherit from policy_base, which provides default implementations of submission and initialization logic.

policy_base uses the Curiously Recurring Template Pattern (CRTP), where the derived policy class passes itself as the first template parameter. This allows policy_base to call derived class methods (like try_select) without virtual function overhead.

namespace oneapi::dpl::experimental {

  template<typename Policy, typename ResourceAdapter, typename Backend,
           typename... ReportReqs>
  class policy_base {
  public:
    using resource_type = /* backend resource type */;

    // Initialization
    void initialize();
    void initialize(const std::vector<resource_type>& u);
    void initialize(const std::vector<resource_type>& u,
                    ResourceAdapter adapter, Args... args);

    // Submission operations
    auto submit(Function&& f, Args&&... args);           // Retries until success
    void submit_and_wait(Function&& f, Args&&... args);  // Blocks until complete
    auto try_submit(Function&& f, Args&&... args);       // Returns std::optional

    // Queries
    auto get_resources() const;
    auto get_submission_group();

  protected:
    std::shared_ptr<Backend> backend_;
  };

}

When using policy_base, your custom policy must implement:

  • try_select(Args...) - Returns std::optional<selection_type>, empty if no resource available

  • initialize_state(Args...) - Performs policy-specific initialization

The policy_base automatically provides the required backend_type and resource_type aliases.

Initialization#

The initialize_state() function is called after the backend is initialized. Use it to set up policy-specific state using resources from get_resources().

Selection Logic#

The try_select() function implements your selection algorithm:

  • Returns std::optional<selection_type> with selected resource

  • Returns std::nullopt if no resource is currently available

  • May accept additional arguments for selection hints

Selection Type#

The selection_type represents a selected resource and encapsulates the policy and resource information. It must satisfy the Selection requirements:

  • unwrap() - Returns the resource object (e.g., sycl::queue) the selection represents

  • get_policy() - Returns the policy that created the selection

  • optional report(i) and report(i, v) - Report execution information back to the policy if required

For policies that do not require execution information reporting (such as simple round_robin_policy), you may use the provided basic_selection_handle_t:

template<typename Policy, typename Resource>
class basic_selection_handle_t {
public:
  explicit basic_selection_handle_t(const Policy& p, Resource e);
  Resource unwrap();
  Policy get_policy();
};

For policies that need execution information (like dynamic_load_policy which tracks task submissions and completions, or auto_tune_policy which measures task timing), define a custom selection type with report() methods, as is shown in the following example:

template<typename Policy, typename Backend>
class custom_selection_handle_t {
  Policy policy_;
  resource_type resource_;

  using scratch_space_t =
      typename backend_traits<Backend>::template selection_scratch_t<
          execution_info::task_submission_t, execution_info::task_completion_t>;
  scratch_space_t scratch_space;

public:
  custom_selection_handle_t(const Policy& p, resource_type r)
    : policy_(p), resource_(std::move(r)) {}

  auto unwrap() { return oneapi::dpl::experimental::unwrap(resource_); }
  Policy get_policy() { return policy_; }

  // Report execution events
  void report(const execution_info::task_submission_t&) const {
    // Handle task submission event
  }
  void report(const execution_info::task_completion_t&) const {
    // Handle task completion event
  }
};

The backend will call the selection handle’s report() methods when execution events occur, allowing the policy to update its state accordingly.

As shown above, for policies that need execution information, the selection handle must also include a member named scratch_space with type dictated by backend_traits<Backend>::template selection_scratch_t<Reqs...> where Reqs... is a variadic pack of all execution information requirements. The backend will use this scratch_space member to store temporary instrumentation data (like profiling events) needed to satisfy the reporting requirement. For more information, see Selection Scratch Space.

Reporting Requirements#

If your policy needs execution information (like task completion times), specify reporting requirements as template parameters to policy_base:

class timing_aware_policy
  : public ex::policy_base<timing_aware_policy,
                           oneapi::dpl::identity,
                           ex::default_backend<sycl::queue>,
                           ex::execution_info::task_time_t> {
  // Policy implementation that receives timing information
};

Execution Information#

Backends can provide execution information to policies for making informed selection decisions. The oneapi::dpl::experimental::execution_info namespace contains tag types and tag objects that describe the instrumentation information policies require for their selection logic. Policies specify their requirements using these tags during backend construction. Backends then call report with these tags to provide the requested execution information to the policy via selection objects.

The following execution information types are available:

Tag Type

Tag Object

Value Type

Description

task_submission_t

task_submission

void

Signals when a task is submitted

task_completion_t

task_completion

void

Signals when a task completes

task_time_t

task_time

std::chrono::milliseconds

Elapsed time from submission to completion

Built-In Policy Requirements#

The following table shows the reporting requirements for each built-in policy:

Policy

Reporting Requirements

fixed_resource_policy

None

round_robin_policy

None

dynamic_load_policy

task_submission, task_completion

auto_tune_policy

task_time

Policies with no reporting requirements can work with any backend, including the provided generic backend implementation which is used when no specialization of core_resource_backend exists for the specific resource. Policies with reporting requirements need a backend that supports those specific types of execution information.

Policies with reporting requirements must call lazy_report() prior to selection, if the backend supports it. Lazy Reporting allows backends to update their execution information state before making selection decisions. See dynamic_load_policy and auto_tune_policy for examples of this.

Policy State Reference Semantics#

Best practice is to make your policy’s selection state stored in a shared_ptr to enable common reference semantics - copies of your policy will share the same state, as set up by initialize_state calls.

struct selector_t {
  // Policy-specific selection state
};
std::shared_ptr<selector_t> selector_;

Examples#

For examples please look to the existing policies within oneDPL (fixed_resource_policy, round_robin_policy, dynamic_load_policy, auto_tune_policy), which are all written using policy_base and according to these best practices.

See Also#

  • Backends - Overview of the backend concept

  • Policies - Overview of the policy concept

  • Functions - Free functions for working with policies