Skip to content

[SYCL][1/2] Submit an already built sycl::kernel without a handler - #22970

Merged
uditagarwal97 merged 7 commits into
intel:syclfrom
mieshkiwrk:kernel-fastpath-variadic
Sep 14, 2026
Merged

uditagarwal97 merged 7 commits into
intel:syclfrom
mieshkiwrk:kernel-fastpath-variadic

Conversation

@mieshkiwrk

@mieshkiwrk mieshkiwrk commented Aug 18, 2026 •

Copy link
Copy Markdown
Contributor

nd_launch(queue, nd_range, const kernel &, args...) expands to submit() plus a handler, so a kernel object never reaches the direct submission path that a kernel function object already takes. For a language runtime that loads kernels from a binary, the handler-less enqueue functions therefore save nothing.

Bind the arguments straight from the call and reuse queue_impl::submit_kernel_scheduler_bypass with a real kernel_impl *, which that function already accepts, whenever every argument can be bound as plain bytes. Accessors, local accessors, streams and work group memory keep the command group path, the same line HasSpecialCaptures draws in the runtime, and a dependency the scheduler has to track still falls back to a command group.

KernelArgView is passed across the ABI boundary, so it gets its own header in an inline versioned namespace with a layout test, following nd_range_view.


Arrays were classified through std::decay_t, so an array argument was bound as a pointer and the runtime read its first bytes as an address. Classification no longer decays, and makeKernelArgView binds an array as the bytes it is, which is what handler::setArgHelper does with it.

A USM pointer passed as raw_kernel_arg binds as a value argument, which only works on Level Zero - pre-existing, it reproduces on the released handler path. The test now passes the pointer typed, with the all-raw case gated on Level Zero. The bundle travels to the direct submission as it does through the handler.


Review fixes. cl_mem is a pointer typedef, so it was bound as a pointer argument and the handle reached clSetKernelArgMemPointerINTEL as if it were an address, which faults on the OpenCL backend; it now binds as the bytes of the handle, the exception handler::setArgHelper makes for OpenCLMemT.

An array of scalars now reaches the fast path, which no trait let it do, so the binding above is the one it gets. Class types, and arrays of them, keep the command group path: those may be structs with special types inside.

Neither which arguments take the fast path nor the kind they are bound with is observable end to end, hence kernel_arg_classification.cpp.


More context: intel/intel-xpu-backend-for-triton#7737

nd_launch(queue, nd_range, const kernel &, args...) expands to submit() plus a
handler, so a kernel object never reaches the direct submission path that a
kernel function object already takes. For a language runtime that loads kernels
from a binary, the handler-less enqueue functions therefore save nothing.

Bind the arguments straight from the call and reuse
queue_impl::submit_kernel_scheduler_bypass with a real kernel_impl *, which that
function already accepts, whenever every argument can be bound as plain bytes.
Accessors, local accessors, streams and work group memory keep the command group
path, the same line HasSpecialCaptures draws in the runtime, and a dependency the
scheduler has to track still falls back to a command group.

KernelArgView is passed across the ABI boundary, so it gets its own header in an
inline versioned namespace with a layout test, following nd_range_view.
`is_plain_kernel_arg_v` classified through `std::decay_t`, which turns an array
into a pointer, so `nd_launch(queue, range, kernel, array, ...)` bound the array
as UR_EXP_KERNEL_ARG_TYPE_POINTER and the runtime read its first bytes as an
address. That binds neither the bytes nor the array: wrong results on Level Zero
and an abort inside the OpenCL driver, where the handler path binds the array as
plain bytes. Classify without decaying, so an array keeps using the command
group path, and cover both paths with a test.

The E2E test also passed a USM pointer through `raw_kernel_arg`, which binds as
a value argument and therefore only reaches the kernel on Level Zero. The
pointer is now passed typed, and the all-raw case moved to a Level Zero gated
test, the same restriction the RawKernelArg tests carry.

Pass the kernel bundle to the direct submission the way the handler path passes
it, so the device globals a bundle keeps to itself can be initialized.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds direct submission for prebuilt sycl::kernel objects while preserving scheduler fallback for special arguments and dependencies.

Changes:

  • Introduces ABI-stable kernel argument views and a direct runtime submission path.
  • Classifies and binds plain arguments without constructing a handler.
  • Adds ABI and end-to-end coverage for typed, raw, accessor, scratch-memory, and array arguments.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
sycl/test/abi/sycl_symbols_windows.dump Records the new Windows ABI symbol.
sycl/test/abi/sycl_symbols_linux.dump Records the new Linux ABI symbol.
sycl/test/abi/layout_kernel_arg_view.cpp Verifies argument-view ABI layout.
sycl/test-e2e/EnqueueFunctions/nd_launch_kernel_obj_direct.cpp Tests direct and fallback launches.
sycl/test-e2e/EnqueueFunctions/nd_launch_kernel_obj_direct_raw_ptr.cpp Tests raw pointer arguments on Level Zero.
sycl/test-e2e/EnqueueFunctions/nd_launch_kernel_obj_array_arg.cpp Tests array argument handling.
sycl/source/queue.cpp Bridges the exported API to queue_impl.
sycl/source/detail/queue_impl.hpp Declares kernel-object direct submission.
sycl/source/detail/queue_impl.cpp Implements bypass and scheduler fallback.
sycl/include/sycl/queue.hpp Exposes the internal submission entry point.
sycl/include/sycl/ext/oneapi/experimental/raw_kernel_arg.hpp Adds raw-argument access helpers.
sycl/include/sycl/ext/oneapi/experimental/enqueue_functions.hpp Routes eligible launches directly.
sycl/include/sycl/detail/kernel_arg_view.hpp Defines the ABI argument representation.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread sycl/include/sycl/ext/oneapi/experimental/enqueue_functions.hpp Outdated

@iclsrc iclsrc left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean implementation of the handler-less nd_launch fast path. The two-commit structure is clear: commit 1 wires up the infrastructure (submit_kernel_obj_direct_without_event + the span-based arg view), commit 2 fixes the array-as-pointer misclassification by switching from std::decay_t to plain_arg_t. ABI versioning via kernel_arg_view_v1 inline namespace is the right approach and the layout test pins the struct correctly.

One pattern inconsistency flagged below; everything else looks solid.

Comment thread sycl/source/detail/queue_impl.cpp Outdated
Comment thread sycl/source/detail/queue_impl.cpp
Comment thread sycl/include/sycl/ext/oneapi/experimental/enqueue_functions.hpp Outdated
`cl_mem` is a pointer typedef, so it was bound as a pointer argument and the
handle reached clSetKernelArgMemPointerINTEL as if it were an address, which
faults on the OpenCL backend. Bind it as the bytes of the handle, the exception
`handler::setArgHelper` makes for `OpenCLMemT`.

`makeKernelArgView` already bound an array as the bytes it is, but no trait let
one reach the fast path, so an array argument still went through the handler.
Class types, and arrays of them, keep the command group path: those may be
structs with special types inside.

Neither which arguments take the fast path nor the kind they are bound with is
observable end to end, hence the new test.

Extract the `KData` fields before the `CGExecKernel` construction that moves it,
the way `submit_kernel_direct_impl` does, and say why the argument pointer can be
cast.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this is also a problem in the existing code, but why aren't these functions in the detail namespace? In general, we shouldn't define identifiers directly in the sycl namespace unless they are part of the public API.

This might be a question that is more for @intel/llvm-reviewers-runtime .

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, we should move these functions to the detail namespace. I've opened a JIRA, CMPLRLLVM-77982, to review and fix all such cases in the next ABI breaking window.

@mieshkiwrk

Copy link
Copy Markdown
Contributor Author

Friendly reminder bump

@uditagarwal97 uditagarwal97 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SYCL changes LGTM overall, just a few NITs

Comment thread sycl/include/sycl/ext/oneapi/experimental/enqueue_functions.hpp Outdated
Comment thread sycl/include/sycl/ext/oneapi/experimental/enqueue_functions.hpp Outdated
Comment thread sycl/include/sycl/ext/oneapi/experimental/enqueue_functions.hpp Outdated
Comment on lines 113 to 118

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed, along with the two lines left over after it - the trait now says in three lines what it accepts.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the name "plain" kernel arguments is ambiguous - can we have a more descriptive name instead? Something that suggests that this trait filters out arguments of type Accessors, Streams, etc.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed to is_direct_kernel_arg_v, with what it accepts spelled out above it. Not named after special captures, since the trait is also false for a plain struct that has none.

Also renamed plain_arg_kind_v to kernel_arg_kind_v, so no name is left calling an argument plain - say if you would rather keep it.

Comment thread sycl/include/sycl/ext/oneapi/experimental/enqueue_functions.hpp Outdated
Comment thread sycl/include/sycl/ext/oneapi/experimental/enqueue_functions.hpp Outdated
Comment thread sycl/include/sycl/ext/oneapi/experimental/enqueue_functions.hpp Outdated
mieshkiwrk and others added 3 commits September 9, 2026 09:56
Co-authored-by: Udit Kumar Agarwal <udit.agarwal@intel.com>
Applying the suggestion replaced only its last line, so the comment kept a
sentence that ended mid-way and repeated the first one. It is a single line now,
as the suggestion intended, while still saying that an array is not decayed,
which is the reason this alias exists.
Rename `plain_arg_t` to `unqualified_arg_t` and `is_plain_kernel_arg_v` to
`is_direct_kernel_arg_v`, which says what the trait selects for, and rename
`plain_arg_kind_v` to `kernel_arg_kind_v` so that no name is left describing an
argument as plain. Shorten the comments on the traits and on the pointer kind,
say above the `if constexpr` what each of its branches is for, and drop the
notes on binding the arguments in place and on seeding the code location, both
of which the code says on its own.
Comment on lines 115 to 120

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT: Can we simplify this type trait by calling unqualified_arg_t<T> once and then reusing the unqualified type?
Similar simplifications can be done in the following traits as well.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done - a defaulted template parameter in the traits, a local alias in makeKernelArgView.

@uditagarwal97 uditagarwal97 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

The traits spelled `unqualified_arg_t<T>` again for every use of the unqualified
type. They take it as a defaulted template parameter now, and makeKernelArgView
keeps it in a local alias, so each of them names it once.
@mieshkiwrk

Copy link
Copy Markdown
Contributor Author

The three red CI checks are known Arc/Windows flakes, two of them green on the previous commit, which differs only by comments and an alias refactor.

@uditagarwal97

Copy link
Copy Markdown
Contributor

The three red CI checks are known Arc/Windows flakes, two of them green on the previous commit, which differs only by comments and an alias refactor.

Yes, CI failures are unrelated. Merging the PR.

@uditagarwal97
uditagarwal97 merged commit f3a8b1a into intel:sycl Sep 14, 2026
27 of 30 checks passed
@mieshkiwrk

Copy link
Copy Markdown
Contributor Author

Thanks for the review!

uditagarwal97 pushed a commit that referenced this pull request Oct 21, 2026
… `std::span` (#23480)

Implements #23479. Follow-up to #22970, earlier review in #22971.

The parameter pack overload needs one instantiation per argument count,
so a runtime that builds the argument list at run time, like Triton,
needs a dispatch table with a cap. The new overload takes a
`std::span<const raw_kernel_arg>` and keeps the direct submission path
from #22970; pointer arguments use the `pointer_arg` form. A sequence
passed to the parameter pack overload goes to the span overload, and
before C++20 it is rejected with a `static_assert`. The entry point
exported by #22970 now takes a pointer and a count, so no span type is
part of the ABI.

More context:
intel/intel-xpu-backend-for-triton#7737
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants