From ad08858d948ed373bb7a3e601ff1125c6a641f68 Mon Sep 17 00:00:00 2001 From: Artur Gainullin Date: Thu, 15 Oct 2026 17:19:18 -0700 Subject: [PATCH 1/3] [UR][L0v2] Match the priority flags to the ends of the priority range The low and high priority flags didn't match the ends of the reported priority range, and devices without priority levels didn't support numeric priorities at all although they support the flags: - zeDeviceGetPriorityLevels only reports the levels of the context group, while ZE_COMMAND_QUEUE_PRIORITY_PRIORITY_LOW uses a separate context that runs below all of them, so the low flag had no numeric equivalent. - The high priority class alone gets the driver's default priority of high priority queues, which may be below the highest priority level. Report one more level below the driver range and create queues at that level with the low priority class and without a priority level, since the driver derives the class from a level. Use the highest level for the high priority flag. Without driver levels, report the range [-1, 1] made of the high, normal and low priority classes, like the v1 adapter. Assisted-By: Claude --- .../adapters/level_zero/common/device.cpp | 30 +++--- .../adapters/level_zero/common/device.hpp | 62 ++++++++++++- .../source/adapters/level_zero/v2/device.cpp | 5 +- .../adapters/level_zero/v2/queue_create.cpp | 40 +++----- .../adapters/level_zero/v2/CMakeLists.txt | 4 + .../level_zero/v2/command_list_cache_test.cpp | 72 ++++++++++++-- .../level_zero/v2/queue_priority_test.cpp | 93 +++++++++++++++++++ 7 files changed, 248 insertions(+), 58 deletions(-) create mode 100644 unified-runtime/test/adapters/level_zero/v2/queue_priority_test.cpp diff --git a/unified-runtime/source/adapters/level_zero/common/device.cpp b/unified-runtime/source/adapters/level_zero/common/device.cpp index dc6588f29a604..16e5d01672427 100644 --- a/unified-runtime/source/adapters/level_zero/common/device.cpp +++ b/unified-runtime/source/adapters/level_zero/common/device.cpp @@ -2177,32 +2177,26 @@ ur_result_t ur_device_handle_t_::initialize(int SubSubDeviceOrdinal, ZeQueuePriorityRange.Compute = [this, ZeDevice](ze_queue_priority_range &Range) { + std::optional> DriverLevels; + int32_t Lowest = 0, Highest = 0; auto GetPriorityLevels = Platform->ZeQueuePriorityExt.zeDeviceGetPriorityLevels; - if (!GetPriorityLevels) - return; // The function is loaded directly from the driver, so it expects a driver // handle rather than a loader handle. ze_device_handle_t DriverDevice = nullptr; - if (ZE_CALL_NOCHECK(zelLoaderTranslateHandle, + if (GetPriorityLevels && + ZE_CALL_NOCHECK(zelLoaderTranslateHandle, (ZEL_HANDLE_DEVICE, ZeDevice, - reinterpret_cast(&DriverDevice))) != - ZE_RESULT_SUCCESS || - ZE_CALL_NOCHECK(GetPriorityLevels, - (DriverDevice, &Range.Lowest, &Range.Highest)) != + reinterpret_cast(&DriverDevice))) == + ZE_RESULT_SUCCESS && + ZE_CALL_NOCHECK(GetPriorityLevels, (DriverDevice, &Lowest, &Highest)) == ZE_RESULT_SUCCESS) - return; - // A single level or an empty range means there are no levels to choose - // from. - if (Range.Highest >= Range.Lowest) - return; + DriverLevels = {Lowest, Highest}; // The UR API guarantees that the range contains the default priority 0. - if (Range.Highest > 0 || Range.Lowest < 0) { - UR_LOG(WARN, "Ignoring queue priority range [{}, {}] without 0.", - Range.Highest, Range.Lowest); - return; - } - Range.Supported = true; + if (DriverLevels && (Highest > 0 || Lowest < 0)) + UR_LOG(WARN, "Ignoring queue priority range [{}, {}] without 0.", Highest, + Lowest); + Range = makeQueuePriorityRange(DriverLevels); }; #ifdef ZE_INTEL_DEVICE_BLOCK_ARRAY_EXP_NAME diff --git a/unified-runtime/source/adapters/level_zero/common/device.hpp b/unified-runtime/source/adapters/level_zero/common/device.hpp index 5cefd5af91efc..3e0fe8a2f736a 100644 --- a/unified-runtime/source/adapters/level_zero/common/device.hpp +++ b/unified-runtime/source/adapters/level_zero/common/device.hpp @@ -8,6 +8,7 @@ //===----------------------------------------------------------------------===// #pragma once +#include #include #include #include @@ -17,6 +18,7 @@ #include #include #include +#include #include #include "api.hpp" @@ -69,13 +71,69 @@ struct ze_global_memsize { uint64_t value; }; -// Queue priority range reported by zeDeviceGetPriorityLevels. +// Queue priority range, see makeQueuePriorityRange. struct ze_queue_priority_range { - bool Supported; int32_t Highest; + // Maps to ZE_COMMAND_QUEUE_PRIORITY_PRIORITY_LOW instead of a priority level. int32_t Lowest; + // Whether the levels above Lowest are the levels of + // zeDeviceGetPriorityLevels. Otherwise the range is {1, -1}, and -1 maps to + // ZE_COMMAND_QUEUE_PRIORITY_PRIORITY_HIGH instead of a priority level. + bool HasLevels; }; +// Makes the queue priority range from the {lowest, highest} levels reported by +// zeDeviceGetPriorityLevels, if any. The driver levels only cover the context +// group, while the low priority class uses a separate context below all of +// them, so it's exposed as an extra lowest level. Without at least two levels +// around the default priority 0, the range is made of the low, normal and high +// priority classes instead. +inline ze_queue_priority_range makeQueuePriorityRange( + std::optional> DriverLevels) { + if (!DriverLevels) + return {-1, 1, false}; + const auto [Lowest, Highest] = *DriverLevels; + if (Highest >= Lowest || Highest > 0 || Lowest < 0) + return {-1, 1, false}; + return {Highest, Lowest + 1, true}; +} + +// The Level Zero priority class and level of a queue. +struct ze_queue_priority { + ze_command_queue_priority_t Class; + std::optional Level; +}; + +// Returns the Level Zero priority of a queue created with the given flags or, +// if set, the given numeric priority. +inline ze_queue_priority +getZeQueuePriority(const ze_queue_priority_range &Range, ur_queue_flags_t Flags, + std::optional Priority) { + if (!Priority) { + if (Flags & UR_QUEUE_FLAG_PRIORITY_LOW) + return {ZE_COMMAND_QUEUE_PRIORITY_PRIORITY_LOW, std::nullopt}; + if (!(Flags & UR_QUEUE_FLAG_PRIORITY_HIGH)) + return {ZE_COMMAND_QUEUE_PRIORITY_NORMAL, std::nullopt}; + // The high priority class alone gets the driver's default priority of high + // priority queues, which may be below the highest priority level. + if (Range.HasLevels && Range.Highest < 0) + return {ZE_COMMAND_QUEUE_PRIORITY_NORMAL, Range.Highest}; + return {ZE_COMMAND_QUEUE_PRIORITY_PRIORITY_HIGH, std::nullopt}; + } + // Clamp here rather than relying on the driver, so that values mapping to + // the same priority also share cached command lists. + const int32_t Level = std::clamp(*Priority, Range.Highest, Range.Lowest); + // The driver derives the priority class from a priority level, so the + // lowest level can only be expressed by the low class without a level. + if (Level == Range.Lowest) + return {ZE_COMMAND_QUEUE_PRIORITY_PRIORITY_LOW, std::nullopt}; + if (Range.HasLevels) + return {ZE_COMMAND_QUEUE_PRIORITY_NORMAL, Level}; + return {Level < 0 ? ZE_COMMAND_QUEUE_PRIORITY_PRIORITY_HIGH + : ZE_COMMAND_QUEUE_PRIORITY_NORMAL, + std::nullopt}; +} + enum ur_ze_external_memory_desc_type { UR_ZE_EXTERNAL_OPAQUE_FD, UR_ZE_EXTERNAL_WIN32, diff --git a/unified-runtime/source/adapters/level_zero/v2/device.cpp b/unified-runtime/source/adapters/level_zero/v2/device.cpp index 452bf9cfdcb70..aea19c26046b8 100644 --- a/unified-runtime/source/adapters/level_zero/v2/device.cpp +++ b/unified-runtime/source/adapters/level_zero/v2/device.cpp @@ -83,13 +83,10 @@ ur_result_t urDeviceGetInfo(::ur_device_handle_t hDeviceOpque, #endif } case UR_DEVICE_INFO_QUEUE_PRIORITY_SUPPORT_EXP: - return ReturnValue( - static_cast(hDevice->ZeQueuePriorityRange->Supported)); + return ReturnValue(static_cast(true)); case UR_DEVICE_INFO_QUEUE_PRIORITY_HIGHEST_EXP: case UR_DEVICE_INFO_QUEUE_PRIORITY_LOWEST_EXP: { const auto &Range = hDevice->ZeQueuePriorityRange.get(); - if (!Range.Supported) - return UR_RESULT_ERROR_UNSUPPORTED_ENUMERATION; return ReturnValue(propName == UR_DEVICE_INFO_QUEUE_PRIORITY_HIGHEST_EXP ? Range.Highest : Range.Lowest); diff --git a/unified-runtime/source/adapters/level_zero/v2/queue_create.cpp b/unified-runtime/source/adapters/level_zero/v2/queue_create.cpp index 955f467ebb520..21d94d5ac483c 100644 --- a/unified-runtime/source/adapters/level_zero/v2/queue_create.cpp +++ b/unified-runtime/source/adapters/level_zero/v2/queue_create.cpp @@ -43,14 +43,6 @@ getPriorityLevel(const ur_queue_properties_t *pProps) { return std::nullopt; } -static ze_command_queue_priority_t getZePriority(ur_queue_flags_t flags) { - if ((flags & UR_QUEUE_FLAG_PRIORITY_LOW) != 0) - return ZE_COMMAND_QUEUE_PRIORITY_PRIORITY_LOW; - if ((flags & UR_QUEUE_FLAG_PRIORITY_HIGH) != 0) - return ZE_COMMAND_QUEUE_PRIORITY_PRIORITY_HIGH; - return ZE_COMMAND_QUEUE_PRIORITY_NORMAL; -} - static event_flags_t eventFlagsFromQueueFlags(ur_queue_flags_t flags) { event_flags_t eventFlags = EVENT_FLAGS_COUNTER; if (flags & UR_QUEUE_FLAG_PROFILING_ENABLE) @@ -104,37 +96,29 @@ ur_result_t urQueueCreate(::ur_context_handle_t hContextOpque, } auto zeIndex = v2::getZeIndex(pProperties); - auto priorityLevel = v2::getPriorityLevel(pProperties); - - if (priorityLevel) { - if (flags & (UR_QUEUE_FLAG_PRIORITY_LOW | UR_QUEUE_FLAG_PRIORITY_HIGH)) - return UR_RESULT_ERROR_INVALID_QUEUE_PROPERTIES; - const auto &Range = hDevice->ZeQueuePriorityRange.get(); - if (!Range.Supported) - return UR_RESULT_ERROR_UNSUPPORTED_FEATURE; - // Clamp here rather than relying on the driver, so that values mapping to - // the same priority also share cached command lists. - priorityLevel = std::clamp(*priorityLevel, Range.Highest, Range.Lowest); - } + auto priority = v2::getPriorityLevel(pProperties); + if (priority && + (flags & (UR_QUEUE_FLAG_PRIORITY_LOW | UR_QUEUE_FLAG_PRIORITY_HIGH))) + return UR_RESULT_ERROR_INVALID_QUEUE_PROPERTIES; + const auto [zePriority, priorityLevel] = + getZeQueuePriority(hDevice->ZeQueuePriorityRange.get(), flags, priority); if (isBatched) { // out of order not supported *phQueue = ur_queue_handle_t_::create( - hContext, hDevice, v2::getZeOrdinal(hDevice), v2::getZePriority(flags), - zeIndex, priorityLevel, v2::eventFlagsFromQueueFlags(flags), flags); + hContext, hDevice, v2::getZeOrdinal(hDevice), zePriority, zeIndex, + priorityLevel, v2::eventFlagsFromQueueFlags(flags), flags); } else { if (isOutOfOrder) { *phQueue = ur_queue_handle_t_::create( - hContext, hDevice, v2::getZeOrdinal(hDevice), - v2::getZePriority(flags), zeIndex, priorityLevel, - v2::eventFlagsFromQueueFlags(flags), flags); + hContext, hDevice, v2::getZeOrdinal(hDevice), zePriority, zeIndex, + priorityLevel, v2::eventFlagsFromQueueFlags(flags), flags); } else { // immediate *phQueue = ur_queue_handle_t_::create( - hContext, hDevice, v2::getZeOrdinal(hDevice), - v2::getZePriority(flags), zeIndex, priorityLevel, - v2::eventFlagsFromQueueFlags(flags), flags); + hContext, hDevice, v2::getZeOrdinal(hDevice), zePriority, zeIndex, + priorityLevel, v2::eventFlagsFromQueueFlags(flags), flags); } } diff --git a/unified-runtime/test/adapters/level_zero/v2/CMakeLists.txt b/unified-runtime/test/adapters/level_zero/v2/CMakeLists.txt index 84ba44c9ab912..71f3bd6b840a9 100644 --- a/unified-runtime/test/adapters/level_zero/v2/CMakeLists.txt +++ b/unified-runtime/test/adapters/level_zero/v2/CMakeLists.txt @@ -57,6 +57,10 @@ add_l0_v2_devices_test(batched_queue batched_queue_test.cpp ) +add_l0_v2_devices_test(queue_priority + queue_priority_test.cpp +) + if(NOT UR_FOUND_DPCXX) # Tests that require kernels can't be used if we aren't generating # device binaries diff --git a/unified-runtime/test/adapters/level_zero/v2/command_list_cache_test.cpp b/unified-runtime/test/adapters/level_zero/v2/command_list_cache_test.cpp index 301622b7db4f3..afa547cd29b71 100644 --- a/unified-runtime/test/adapters/level_zero/v2/command_list_cache_test.cpp +++ b/unified-runtime/test/adapters/level_zero/v2/command_list_cache_test.cpp @@ -99,8 +99,8 @@ TEST_P(CommandListCacheTest, CanStoreAndRetriveImmediateAndRegularCmdLists) { } TEST_P(CommandListCacheTest, ImmediateCommandListsAreCachedPerPriorityLevel) { - if (!common_cast(device)->ZeQueuePriorityRange->Supported) { - GTEST_SKIP() << "Queue priority levels are not supported."; + if (!common_cast(device)->ZeQueuePriorityRange->HasLevels) { + GTEST_SKIP() << "Device has no priority levels."; } v2::supported_extensions_descriptor_t supportedExtensions(false, false, @@ -291,10 +291,6 @@ TEST_P(CommandListCacheTest, CommandListsAreReusedByQueues) { TEST_P(CommandListCacheTest, OutOfRangePriorityLevelsAreClamped) { const auto &Range = common_cast(device)->ZeQueuePriorityRange.get(); - if (!Range.Supported) { - GTEST_SKIP() << "Queue priority levels are not supported."; - } - auto &clCache = v2::v2_cast(context)->getCommandListCache(); auto createQueue = [&](int32_t Priority, uur::raii::Queue &Queue) { ur_exp_queue_priority_properties_t PriorityProps{ @@ -324,6 +320,70 @@ TEST_P(CommandListCacheTest, OutOfRangePriorityLevelsAreClamped) { } } +TEST_P(CommandListCacheTest, LowestPriorityLevelUsesLowPriorityClass) { + auto hDevice = common_cast(device); + const auto &Range = hDevice->ZeQueuePriorityRange.get(); + int32_t Lowest = 0; + ASSERT_SUCCESS(::urDeviceGetInfo(device, + UR_DEVICE_INFO_QUEUE_PRIORITY_LOWEST_EXP, + sizeof(Lowest), &Lowest, nullptr)); + ASSERT_EQ(Lowest, Range.Lowest); + + { + ur_exp_queue_priority_properties_t PriorityProps{ + UR_STRUCTURE_TYPE_EXP_QUEUE_PRIORITY_PROPERTIES, nullptr, Lowest}; + ur_queue_properties_t QueueProps{UR_STRUCTURE_TYPE_QUEUE_PROPERTIES, + &PriorityProps, + UR_QUEUE_FLAG_SUBMISSION_IMMEDIATE}; + uur::raii::Queue Queue; + ASSERT_SUCCESS(urQueueCreate(context, device, &QueueProps, Queue.ptr())); + } + + // The queue's command list must have been created with the low priority + // class and without a priority level. + auto &clCache = v2::v2_cast(context)->getCommandListCache(); + const size_t NumCached = clCache.getNumImmediateCommandLists(); + v2::command_list_desc_t Desc; + Desc.IsInOrder = true; + using queue_group_type = + ur::level_zero::ur_device_handle_t_::queue_group_info_t::type; + Desc.Ordinal = hDevice->QueueGroup[queue_group_type::Compute].ZeOrdinal; + Desc.CopyOffloadEnable = true; + auto CmdList = clCache.getImmediateCommandList( + hDevice->ZeDevice, Desc, ZE_COMMAND_QUEUE_MODE_ASYNCHRONOUS, + ZE_COMMAND_QUEUE_PRIORITY_PRIORITY_LOW); + ASSERT_NE(CmdList, nullptr); + ASSERT_EQ(clCache.getNumImmediateCommandLists(), NumCached - 1); +} + +TEST_P(CommandListCacheTest, HighPriorityFlagUsesHighestPriorityLevel) { + const auto &Range = common_cast(device)->ZeQueuePriorityRange.get(); + if (!Range.HasLevels || Range.Highest >= 0) { + GTEST_SKIP() << "Device has no priority level above the default one."; + } + + auto &clCache = v2::v2_cast(context)->getCommandListCache(); + const size_t NumCached = clCache.getNumImmediateCommandLists(); + { + ur_queue_properties_t QueueProps{ + UR_STRUCTURE_TYPE_QUEUE_PROPERTIES, nullptr, + UR_QUEUE_FLAG_SUBMISSION_IMMEDIATE | UR_QUEUE_FLAG_PRIORITY_HIGH}; + uur::raii::Queue Queue; + ASSERT_SUCCESS(urQueueCreate(context, device, &QueueProps, Queue.ptr())); + } + ASSERT_GT(clCache.getNumImmediateCommandLists(), NumCached); + + // A queue with the highest priority level must reuse all command lists. + ur_exp_queue_priority_properties_t PriorityProps{ + UR_STRUCTURE_TYPE_EXP_QUEUE_PRIORITY_PROPERTIES, nullptr, Range.Highest}; + ur_queue_properties_t QueueProps{UR_STRUCTURE_TYPE_QUEUE_PROPERTIES, + &PriorityProps, + UR_QUEUE_FLAG_SUBMISSION_IMMEDIATE}; + uur::raii::Queue Queue; + ASSERT_SUCCESS(urQueueCreate(context, device, &QueueProps, Queue.ptr())); + ASSERT_EQ(clCache.getNumImmediateCommandLists(), NumCached); +} + TEST_P(CommandListCacheTest, CommandListsCacheIsThreadSafe) { static constexpr int NumThreads = 10; static constexpr int NumIters = 10; diff --git a/unified-runtime/test/adapters/level_zero/v2/queue_priority_test.cpp b/unified-runtime/test/adapters/level_zero/v2/queue_priority_test.cpp new file mode 100644 index 0000000000000..a53af9d7158d3 --- /dev/null +++ b/unified-runtime/test/adapters/level_zero/v2/queue_priority_test.cpp @@ -0,0 +1,93 @@ +// Part of the LLVM Project, under the Apache License v2.0 with LLVM +// Exceptions. See https://llvm.org/LICENSE.txt for license information. +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +// RUN: %with-v2 ./queue_priority-test +// REQUIRES: v2 + +#include "level_zero/common/device.hpp" + +#include +#include +#include +#include + +using namespace ur::level_zero; + +namespace { + +constexpr int32_t Min = std::numeric_limits::min(); +constexpr int32_t Max = std::numeric_limits::max(); + +void expectPriority(const ze_queue_priority &Actual, + ze_command_queue_priority_t Class, + std::optional Level) { + EXPECT_EQ(Actual.Class, Class); + EXPECT_EQ(Actual.Level, Level); +} + +} // namespace + +TEST(QueuePriorityRangeTest, WithoutDriverLevelsUsesPriorityClasses) { + // No zeDeviceGetPriorityLevels, a single level, or a range without 0. + for (auto DriverLevels : + {std::optional>{}, + std::optional{std::pair{0, 0}}, std::optional{std::pair{2, 1}}}) { + const auto Range = makeQueuePriorityRange(DriverLevels); + EXPECT_EQ(Range.Highest, -1); + EXPECT_EQ(Range.Lowest, 1); + EXPECT_FALSE(Range.HasLevels); + } +} + +TEST(QueuePriorityRangeTest, WithDriverLevelsAddsLowestLevel) { + const auto Range = makeQueuePriorityRange(std::pair{0, -3}); + EXPECT_EQ(Range.Highest, -3); + EXPECT_EQ(Range.Lowest, 1); + EXPECT_TRUE(Range.HasLevels); +} + +TEST(QueuePriorityTest, WithoutDriverLevelsUsesPriorityClasses) { + const auto Range = makeQueuePriorityRange(std::nullopt); + + expectPriority(getZeQueuePriority(Range, 0, std::nullopt), + ZE_COMMAND_QUEUE_PRIORITY_NORMAL, std::nullopt); + expectPriority( + getZeQueuePriority(Range, UR_QUEUE_FLAG_PRIORITY_LOW, std::nullopt), + ZE_COMMAND_QUEUE_PRIORITY_PRIORITY_LOW, std::nullopt); + expectPriority( + getZeQueuePriority(Range, UR_QUEUE_FLAG_PRIORITY_HIGH, std::nullopt), + ZE_COMMAND_QUEUE_PRIORITY_PRIORITY_HIGH, std::nullopt); + + for (int32_t Priority : {1, Max}) + expectPriority(getZeQueuePriority(Range, 0, Priority), + ZE_COMMAND_QUEUE_PRIORITY_PRIORITY_LOW, std::nullopt); + expectPriority(getZeQueuePriority(Range, 0, 0), + ZE_COMMAND_QUEUE_PRIORITY_NORMAL, std::nullopt); + for (int32_t Priority : {-1, Min}) + expectPriority(getZeQueuePriority(Range, 0, Priority), + ZE_COMMAND_QUEUE_PRIORITY_PRIORITY_HIGH, std::nullopt); +} + +TEST(QueuePriorityTest, WithDriverLevelsUsesPriorityLevels) { + const auto Range = makeQueuePriorityRange(std::pair{0, -3}); + + expectPriority(getZeQueuePriority(Range, 0, std::nullopt), + ZE_COMMAND_QUEUE_PRIORITY_NORMAL, std::nullopt); + expectPriority( + getZeQueuePriority(Range, UR_QUEUE_FLAG_PRIORITY_LOW, std::nullopt), + ZE_COMMAND_QUEUE_PRIORITY_PRIORITY_LOW, std::nullopt); + expectPriority( + getZeQueuePriority(Range, UR_QUEUE_FLAG_PRIORITY_HIGH, std::nullopt), + ZE_COMMAND_QUEUE_PRIORITY_NORMAL, -3); + + for (int32_t Priority : {1, Max}) + expectPriority(getZeQueuePriority(Range, 0, Priority), + ZE_COMMAND_QUEUE_PRIORITY_PRIORITY_LOW, std::nullopt); + for (int32_t Priority : {0, -2}) + expectPriority(getZeQueuePriority(Range, 0, Priority), + ZE_COMMAND_QUEUE_PRIORITY_NORMAL, Priority); + expectPriority(getZeQueuePriority(Range, 0, Min), + ZE_COMMAND_QUEUE_PRIORITY_NORMAL, -3); +} From 4db0851681fb010e7f7bb69bf47ef0c8e0713f31 Mon Sep 17 00:00:00 2001 From: Artur Gainullin Date: Thu, 15 Oct 2026 17:19:19 -0700 Subject: [PATCH 2/3] [UR][L0] Support queue priority levels through priority classes The Level Zero v1 adapter supports the low and high priority flags, so also report the priority levels 1, 0 and -1 and create queues with them using the corresponding Level Zero priority classes. Keep the priority class separate from the queue flags, so that UR_QUEUE_INFO_FLAGS only reports the requested flags, and test this for all adapters. Assisted-By: Claude --- .../source/adapters/level_zero/device.cpp | 7 ++++-- .../source/adapters/level_zero/queue.cpp | 24 ++++++++++++++----- .../source/adapters/level_zero/queue.hpp | 7 +++++- .../urQueueCreatePriorityExp.cpp | 20 ++++++++++++++++ 4 files changed, 49 insertions(+), 9 deletions(-) diff --git a/unified-runtime/source/adapters/level_zero/device.cpp b/unified-runtime/source/adapters/level_zero/device.cpp index 5b64f901b97c4..894b1aa6b2f1d 100644 --- a/unified-runtime/source/adapters/level_zero/device.cpp +++ b/unified-runtime/source/adapters/level_zero/device.cpp @@ -36,11 +36,14 @@ ur_result_t urDeviceGetInfo(::ur_device_handle_t hDeviceOpque, return ReturnValue(false); case UR_DEVICE_INFO_IPC_EVENT_SUPPORT_EXP: return ReturnValue(false); + // The low, normal and high priority classes are the priority levels 1, 0 + // and -1, see urQueueCreate. case UR_DEVICE_INFO_QUEUE_PRIORITY_SUPPORT_EXP: - return ReturnValue(static_cast(false)); + return ReturnValue(static_cast(true)); case UR_DEVICE_INFO_QUEUE_PRIORITY_HIGHEST_EXP: + return ReturnValue(int32_t{-1}); case UR_DEVICE_INFO_QUEUE_PRIORITY_LOWEST_EXP: - return UR_RESULT_ERROR_UNSUPPORTED_ENUMERATION; + return ReturnValue(int32_t{1}); default: return ur::level_zero::urDeviceGetInfo(hDeviceOpque, propName, propSize, propValue, propSizeRet); diff --git a/unified-runtime/source/adapters/level_zero/queue.cpp b/unified-runtime/source/adapters/level_zero/queue.cpp index 53532eccd4130..95e734d4f69a5 100644 --- a/unified-runtime/source/adapters/level_zero/queue.cpp +++ b/unified-runtime/source/adapters/level_zero/queue.cpp @@ -501,6 +501,7 @@ ur_result_t urQueueCreate( } int ForceComputeIndex = -1; // Use default/round-robin. + std::optional Priority; if (Props) { if (Props->pNext) { const ur_base_properties_t *extendedDesc = @@ -511,8 +512,12 @@ ur_result_t urQueueCreate( ForceComputeIndex = IndexProperties->computeIndex; } } - if (find_stype_node(Props)) - return UR_RESULT_ERROR_UNSUPPORTED_FEATURE; + if (const auto *PriorityProperties = + find_stype_node(Props)) { + if (Flags & (UR_QUEUE_FLAG_PRIORITY_LOW | UR_QUEUE_FLAG_PRIORITY_HIGH)) + return UR_RESULT_ERROR_INVALID_QUEUE_PROPERTIES; + Priority = PriorityProperties->priority; + } } UR_ASSERT(Context->isValidDevice(Device), UR_RESULT_ERROR_INVALID_DEVICE); @@ -550,7 +555,7 @@ ur_result_t urQueueCreate( try { *Queue = new ur::level_zero::v1::ur_queue_handle_t_( ZeComputeCommandQueues, ZeCopyCommandQueues, Context, Device, true, - Flags, ForceComputeIndex); + Flags, ForceComputeIndex, Priority); } catch (const std::bad_alloc &) { return UR_RESULT_ERROR_OUT_OF_HOST_MEMORY; } catch (...) { @@ -1179,9 +1184,16 @@ ur::level_zero::v1::ur_queue_handle_t_::ur_queue_handle_t_( std::vector &ComputeQueues, std::vector &CopyQueues, ur_context_handle_t Context, ur_device_handle_t Device, - bool OwnZeCommandQueue, ur_queue_flags_t Properties, int ForceComputeIndex) + bool OwnZeCommandQueue, ur_queue_flags_t Properties, int ForceComputeIndex, + std::optional Priority) : Context{Context}, Device{Device}, OwnZeCommandQueue{OwnZeCommandQueue}, Properties(Properties) { + if (Priority) + PriorityClass = std::clamp(*Priority, int32_t{-1}, int32_t{1}); + else if (Properties & UR_QUEUE_FLAG_PRIORITY_LOW) + PriorityClass = 1; + else if (Properties & UR_QUEUE_FLAG_PRIORITY_HIGH) + PriorityClass = -1; // Set the type of commandlists the queue will use when user-selected // submission mode. Otherwise use env var setting and if unset, use default. if (isBatchedSubmission()) @@ -1730,11 +1742,11 @@ bool ur::level_zero::v1::ur_queue_handle_t_::isDiscardEvents() const { } bool ur::level_zero::v1::ur_queue_handle_t_::isPriorityLow() const { - return ((this->Properties & UR_QUEUE_FLAG_PRIORITY_LOW) != 0); + return PriorityClass > 0; } bool ur::level_zero::v1::ur_queue_handle_t_::isPriorityHigh() const { - return ((this->Properties & UR_QUEUE_FLAG_PRIORITY_HIGH) != 0); + return PriorityClass < 0; } bool ur::level_zero::v1::ur_queue_handle_t_::isBatchedSubmission() const { diff --git a/unified-runtime/source/adapters/level_zero/queue.hpp b/unified-runtime/source/adapters/level_zero/queue.hpp index 801e063040279..2052de3c9f77d 100644 --- a/unified-runtime/source/adapters/level_zero/queue.hpp +++ b/unified-runtime/source/adapters/level_zero/queue.hpp @@ -229,7 +229,8 @@ struct ur_queue_handle_t_ : ur_object_t { std::vector &CopyQueues, ur_context_handle_t Context, ur_device_handle_t Device, bool OwnZeCommandQueue, ur_queue_flags_t Properties = 0, - int ForceComputeIndex = -1); + int ForceComputeIndex = -1, + std::optional Priority = std::nullopt); using queue_type = ur_device_handle_t_::queue_group_info_t::type; // PI queue is in general a one to many mapping to L0 native queues. @@ -372,6 +373,10 @@ struct ur_queue_handle_t_ : ur_object_t { // Keeps the properties of this queue. ur_queue_flags_t Properties; + // The priority levels 1, 0 and -1 for the low, normal and high priority + // classes. Set by a numeric priority or by the priority flags. + int32_t PriorityClass = 0; + // Keeps track of whether we are using Counter-based Events bool CounterBasedEventsEnabled = false; diff --git a/unified-runtime/test/conformance/exp_queue_priority/urQueueCreatePriorityExp.cpp b/unified-runtime/test/conformance/exp_queue_priority/urQueueCreatePriorityExp.cpp index 846788969815e..d13d35f3fc059 100644 --- a/unified-runtime/test/conformance/exp_queue_priority/urQueueCreatePriorityExp.cpp +++ b/unified-runtime/test/conformance/exp_queue_priority/urQueueCreatePriorityExp.cpp @@ -109,6 +109,26 @@ TEST_P(urQueueCreatePriorityExpTest, SuccessWithQueueIndex) { UUR_RETURN_ON_FATAL_FAILURE(checkQueue(queue)); } +TEST_P(urQueueCreatePriorityExpTest, FlagsHaveNoPriorityFlags) { + for (int32_t priority : {lowest, 0, highest}) { + uur::raii::Queue queue; + ASSERT_SUCCESS(createQueue(priority, 0, queue.ptr())); + ur_queue_flags_t flags = 0; + ASSERT_SUCCESS(urQueueGetInfo(queue, UR_QUEUE_INFO_FLAGS, sizeof(flags), + &flags, nullptr)); + ASSERT_EQ( + flags & (UR_QUEUE_FLAG_PRIORITY_LOW | UR_QUEUE_FLAG_PRIORITY_HIGH), 0u); + + // The queried flags can be combined with the priority again. + ur_exp_queue_priority_properties_t priorityProps{ + UR_STRUCTURE_TYPE_EXP_QUEUE_PRIORITY_PROPERTIES, nullptr, priority}; + ur_queue_properties_t props{UR_STRUCTURE_TYPE_QUEUE_PROPERTIES, + &priorityProps, flags}; + uur::raii::Queue sameQueue; + ASSERT_SUCCESS(urQueueCreate(context, device, &props, sameQueue.ptr())); + } +} + TEST_P(urQueueCreatePriorityExpTest, InvalidWithPriorityFlags) { for (ur_queue_flags_t flag : {UR_QUEUE_FLAG_PRIORITY_LOW, UR_QUEUE_FLAG_PRIORITY_HIGH}) { From a5eea63751ec774f04a47a25aef23e2a86c86cc6 Mon Sep 17 00:00:00 2001 From: Artur Gainullin Date: Tue, 20 Oct 2026 10:35:21 -0700 Subject: [PATCH 3/3] Address review --- .../source/adapters/level_zero/common/device.hpp | 2 +- .../source/adapters/level_zero/v2/queue_create.cpp | 2 -- .../adapters/level_zero/v2/queue_priority_test.cpp | 12 ++++++++++++ 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/unified-runtime/source/adapters/level_zero/common/device.hpp b/unified-runtime/source/adapters/level_zero/common/device.hpp index 3e0fe8a2f736a..d221b51b8a84e 100644 --- a/unified-runtime/source/adapters/level_zero/common/device.hpp +++ b/unified-runtime/source/adapters/level_zero/common/device.hpp @@ -116,7 +116,7 @@ getZeQueuePriority(const ze_queue_priority_range &Range, ur_queue_flags_t Flags, return {ZE_COMMAND_QUEUE_PRIORITY_NORMAL, std::nullopt}; // The high priority class alone gets the driver's default priority of high // priority queues, which may be below the highest priority level. - if (Range.HasLevels && Range.Highest < 0) + if (Range.HasLevels) return {ZE_COMMAND_QUEUE_PRIORITY_NORMAL, Range.Highest}; return {ZE_COMMAND_QUEUE_PRIORITY_PRIORITY_HIGH, std::nullopt}; } diff --git a/unified-runtime/source/adapters/level_zero/v2/queue_create.cpp b/unified-runtime/source/adapters/level_zero/v2/queue_create.cpp index 21d94d5ac483c..f23d202eb27e5 100644 --- a/unified-runtime/source/adapters/level_zero/v2/queue_create.cpp +++ b/unified-runtime/source/adapters/level_zero/v2/queue_create.cpp @@ -16,8 +16,6 @@ #include "queue_immediate_in_order.hpp" #include "unified-runtime/ur_api.h" -#include - static const bool ForceBatched = getenv_tobool("UR_L0_V2_FORCE_BATCHED"); namespace ur::level_zero::v2 { diff --git a/unified-runtime/test/adapters/level_zero/v2/queue_priority_test.cpp b/unified-runtime/test/adapters/level_zero/v2/queue_priority_test.cpp index a53af9d7158d3..66f76bc4324ba 100644 --- a/unified-runtime/test/adapters/level_zero/v2/queue_priority_test.cpp +++ b/unified-runtime/test/adapters/level_zero/v2/queue_priority_test.cpp @@ -91,3 +91,15 @@ TEST(QueuePriorityTest, WithDriverLevelsUsesPriorityLevels) { expectPriority(getZeQueuePriority(Range, 0, Min), ZE_COMMAND_QUEUE_PRIORITY_NORMAL, -3); } + +TEST(QueuePriorityTest, HighFlagMatchesHighestLevelWhenHighestIsZero) { + const auto Range = makeQueuePriorityRange(std::pair{1, 0}); + ASSERT_TRUE(Range.HasLevels); + ASSERT_EQ(Range.Highest, 0); + + const auto HighFlag = + getZeQueuePriority(Range, UR_QUEUE_FLAG_PRIORITY_HIGH, std::nullopt); + const auto HighestLevel = getZeQueuePriority(Range, 0, Range.Highest); + expectPriority(HighFlag, ZE_COMMAND_QUEUE_PRIORITY_NORMAL, 0); + expectPriority(HighestLevel, HighFlag.Class, HighFlag.Level); +}