clr: Implement dynamic stream to HWq logic (#1958)
* clr: Implement dynamic stream to HW queue assignment This change implements dynamic stream to hardware queue (HWq) mapping with the following features: * Queue depth heuristics with weights for optimal HWq assignment * Make last used queue sticky for better locality * Use pipe HWq to pipe mapping - gfx9 follows a round-robin queue to pipe mapping based on creation order (single process per device only, as pipe ID is statically assigned by runtime) * More aggressive heuristic usage for better queue distribution * Extend dynamic queues support for all stream priorities Environment variables: * DEBUG_HIP_DYNAMIC_QUEUE: 0 - disabled, 1 - Depth heuristics 2 - Depth+Pipe heuristics * DEBUG_HIP_IGNORE_STREAM_PRIORITY=1: ignore priority stream creation * clr: Clean up last_used_queue_
This commit is contained in:
@@ -136,18 +136,22 @@ Device::Device(hsa_agent_t bkendDevice)
|
||||
alloc_granularity_(0),
|
||||
xferQueue_(nullptr),
|
||||
freeMem_(0),
|
||||
vgpusAccess_(true) /* Virtual GPU List Ops Lock */
|
||||
,
|
||||
vgpusAccess_(true), /* Virtual GPU List Ops Lock */
|
||||
hsa_exclusive_gpu_access_(false),
|
||||
queuePool_(QueuePriority::Total),
|
||||
coopHostcallBuffer_(nullptr),
|
||||
queueWithCUMaskPool_(QueuePriority::Total),
|
||||
numOfVgpus_(0),
|
||||
preferred_numa_node_(0),
|
||||
maxSdmaReadMask_(0),
|
||||
maxSdmaWriteMask_(0),
|
||||
sdma_engine_allocator_(*this),
|
||||
cpu_agent_info_(nullptr) {
|
||||
cpu_agent_info_(nullptr),
|
||||
numHwPipes_(4) {
|
||||
// Initialize queue pools with proper comparators (requires 'this' pointer)
|
||||
for (uint i = 0; i < QueuePriority::Total; ++i) {
|
||||
queuePool_.emplace_back(QueueCompare(this));
|
||||
queueWithCUMaskPool_.emplace_back(QueueCompare(this));
|
||||
}
|
||||
|
||||
group_segment_.handle = 0;
|
||||
gpuvm_segment_.handle = 0;
|
||||
gpu_fine_grained_segment_.handle = 0;
|
||||
@@ -225,6 +229,11 @@ Device::~Device() {
|
||||
glb_ctx_ = nullptr;
|
||||
}
|
||||
|
||||
// Destroy transfer queue FIRST (before destroying queues in pool)
|
||||
// because its destructor will call releaseQueue()
|
||||
delete xferQueue_;
|
||||
xferQueue_ = nullptr;
|
||||
|
||||
for (auto& it : queuePool_) {
|
||||
for (auto qIter = it.begin(); qIter != it.end();) {
|
||||
hsa_queue_t* queue = qIter->first;
|
||||
@@ -244,9 +253,6 @@ Device::~Device() {
|
||||
}
|
||||
queuePool_.clear();
|
||||
|
||||
// Destroy transfer queue
|
||||
delete xferQueue_;
|
||||
|
||||
delete blitProgram_;
|
||||
|
||||
if (context_ != nullptr) {
|
||||
@@ -1695,6 +1701,7 @@ device::VirtualDevice* Device::createVirtualDevice(amd::CommandQueue* queue) {
|
||||
|
||||
bool profiling = (queue != nullptr) && queue->properties().test(CL_QUEUE_PROFILING_ENABLE);
|
||||
bool cooperative = false;
|
||||
bool dedicated_queue = (queue != nullptr) && queue->isDedicatedQueue();
|
||||
|
||||
// If amd command queue is null, then it's an internal device queue
|
||||
if (queue == nullptr) {
|
||||
@@ -1708,7 +1715,8 @@ device::VirtualDevice* Device::createVirtualDevice(amd::CommandQueue* queue) {
|
||||
bool q = (queue != nullptr);
|
||||
VirtualGPU* virtualDevice =
|
||||
new VirtualGPU(*this, profiling, cooperative, q ? queue->cuMask() : defaultCuMask,
|
||||
q ? queue->priority() : amd::CommandQueue::Priority::Normal);
|
||||
q ? queue->priority() : amd::CommandQueue::Priority::Normal,
|
||||
dedicated_queue);
|
||||
|
||||
if (!virtualDevice->create()) {
|
||||
delete virtualDevice;
|
||||
@@ -2811,7 +2819,7 @@ VirtualGPU* Device::xferQueue() const {
|
||||
return nullptr;
|
||||
}
|
||||
if (xferQueue_->gpu_queue() == nullptr) {
|
||||
xferQueue_->set_gpu_queue(thisDevice->AcquireActiveNormalQueue());
|
||||
xferQueue_->set_gpu_queue(thisDevice->AcquireActiveQueue(amd::CommandQueue::Priority::Normal));
|
||||
}
|
||||
}
|
||||
xferQueue_->enableSyncBlit();
|
||||
@@ -2863,58 +2871,73 @@ void Device::getHwEventTime(const amd::Event& event, uint64_t* start, uint64_t*
|
||||
}
|
||||
|
||||
// ================================================================================================
|
||||
hsa_queue_t* Device::getQueueFromPool(const uint qIndex) {
|
||||
// Check if queue with refCount 0 is available to use
|
||||
if (queuePool_[qIndex].size() < GPU_MAX_HW_QUEUES) {
|
||||
for (auto& it : queuePool_[qIndex]) {
|
||||
if (it.second.refCount == 0) {
|
||||
it.second.refCount++;
|
||||
ClPrint(amd::LOG_INFO, amd::LOG_QUEUE, "Selected queue refCount: %p (%d)",
|
||||
it.first->base_address, it.second.refCount);
|
||||
return it.first;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (qIndex < QueuePriority::Total && queuePool_[qIndex].size() > 0) {
|
||||
// Search through all available queues for the lowest counter.
|
||||
// Note: the map is sorted in the allocation order for possible round-robin selection
|
||||
typedef decltype(queuePool_)::value_type::const_reference PoolRef;
|
||||
auto lowest = std::min_element(
|
||||
queuePool_[qIndex].begin(), queuePool_[qIndex].end(),
|
||||
[](PoolRef A, PoolRef B) { return A.second.refCount < B.second.refCount; });
|
||||
lowest->second.refCount++;
|
||||
ClPrint(amd::LOG_INFO, amd::LOG_QUEUE, "Selected queue refCount: %p (%d)",
|
||||
lowest->first->base_address, lowest->second.refCount);
|
||||
return lowest->first;
|
||||
}
|
||||
hsa_queue_t* Device::getQueueFromPool(const uint qIndex, bool force_reuse) {
|
||||
// Only reuse queues when we've reached the maximum limit, unless forced
|
||||
// Below the limit, return nullptr to allow creating new queues
|
||||
if (!force_reuse && queuePool_[qIndex].size() < settings().max_hw_queues_) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// We've hit the limit, must reuse - find the queue with lowest load metric
|
||||
if (qIndex < QueuePriority::Total && queuePool_[qIndex].size() > 0) {
|
||||
typedef decltype(queuePool_)::value_type::const_reference PoolRef;
|
||||
|
||||
// Select queue based on dynamic_queues_ mode
|
||||
decltype(queuePool_[qIndex].begin()) lowest;
|
||||
uint32_t mode = settings().dynamic_queues_;
|
||||
|
||||
// gfx9XX pipe distribution: queues map to pipes via queue_id % num_pipes
|
||||
const bool pipe_dist = settings().queue_pipe_dist_;
|
||||
const uint32_t num_pipes = numHwPipes_;
|
||||
|
||||
lowest = std::min_element(
|
||||
queuePool_[qIndex].begin(), queuePool_[qIndex].end(),
|
||||
[mode, pipe_dist, num_pipes](PoolRef A, PoolRef B) {
|
||||
if (mode >= 1) {
|
||||
// Mode 1+: Advanced weighted metric with dedicated queue penalty
|
||||
// Metric = dedicated_queue_penalty + (depth << 4) + refCount
|
||||
uint64_t metricA = A.second.GetLoadMetric(A.first, mode);
|
||||
uint64_t metricB = B.second.GetLoadMetric(B.first, mode);
|
||||
|
||||
if (metricA == metricB && pipe_dist) {
|
||||
// gfx9XX pipe distribution: prefer lower pipe IDs for consistent distribution
|
||||
uint64_t pipeA = A.first->id % num_pipes;
|
||||
uint64_t pipeB = B.first->id % num_pipes;
|
||||
return pipeA < pipeB;
|
||||
}
|
||||
return metricA < metricB;
|
||||
} else {
|
||||
// Mode 0: Simple refCount-based selection
|
||||
return A.second.refCount < B.second.refCount;
|
||||
}
|
||||
});
|
||||
|
||||
lowest->second.refCount++;
|
||||
ClPrint(amd::LOG_INFO, amd::LOG_QUEUE,
|
||||
"Selected queue (mode=%u): %p refCount: %d, depth: %lu, metric: %lu, pipe: %d%s",
|
||||
mode, lowest->first->base_address, lowest->second.refCount,
|
||||
QueueInfo::GetHwQueueDepth(lowest->first),
|
||||
lowest->second.GetLoadMetric(lowest->first, mode),
|
||||
pipe_dist ? (lowest->first->id % num_pipes) : -1,
|
||||
force_reuse ? " (forced)" : "");
|
||||
return lowest->first;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// ================================================================================================
|
||||
hsa_queue_t* Device::AcquireActiveNormalQueue() {
|
||||
hsa_queue_t* Device::AcquireActiveQueue(amd::CommandQueue::Priority priority) {
|
||||
uint32_t queue_size = ROC_AQL_QUEUE_SIZE;
|
||||
auto queue = acquireQueue(queue_size, false, std::vector<uint32_t>{},
|
||||
amd::CommandQueue::Priority::Normal, true);
|
||||
priority, true, false);
|
||||
return queue;
|
||||
}
|
||||
|
||||
// ================================================================================================
|
||||
hsa_queue_t* Device::acquireQueue(uint32_t queue_size_hint, bool coop_queue,
|
||||
const std::vector<uint32_t>& cuMask,
|
||||
amd::CommandQueue::Priority priority, bool managed) {
|
||||
amd::ScopedLock l(active_queue_access_);
|
||||
|
||||
assert(queuePool_[QueuePriority::Low].size() <= GPU_MAX_HW_QUEUES ||
|
||||
queuePool_[QueuePriority::Normal].size() <= GPU_MAX_HW_QUEUES ||
|
||||
queuePool_[QueuePriority::High].size() <= GPU_MAX_HW_QUEUES);
|
||||
|
||||
ClPrint(amd::LOG_INFO, amd::LOG_QUEUE,
|
||||
"Number of allocated hardware queues with low priority: %d,"
|
||||
" with normal priority: %d, with high priority: %d, maximum per priority is: %d",
|
||||
queuePool_[QueuePriority::Low].size(), queuePool_[QueuePriority::Normal].size(),
|
||||
queuePool_[QueuePriority::High].size(), GPU_MAX_HW_QUEUES);
|
||||
|
||||
amd::CommandQueue::Priority priority, bool managed,
|
||||
bool dedicated_queue) {
|
||||
hsa_amd_queue_priority_t queue_priority;
|
||||
uint qIndex;
|
||||
switch (priority) {
|
||||
@@ -2934,22 +2957,49 @@ hsa_queue_t* Device::acquireQueue(uint32_t queue_size_hint, bool coop_queue,
|
||||
break;
|
||||
}
|
||||
|
||||
// If we have reached the max number of queues, reuse an existing queue with the matching queue
|
||||
// priority, choosing the one with the least number of users. Note: Don't attempt to reuse the
|
||||
// cooperative queue, since it's single per device
|
||||
if (!coop_queue && (cuMask.size() == 0) &&
|
||||
((queuePool_[qIndex].size() == GPU_MAX_HW_QUEUES) || queuePool_[qIndex].size() > 0)) {
|
||||
hsa_queue_t* queue = getQueueFromPool(qIndex);
|
||||
if (queue != nullptr) {
|
||||
if (!managed && (qIndex == QueuePriority::Normal)) {
|
||||
num_normal_queues_++;
|
||||
}
|
||||
return queue;
|
||||
}
|
||||
// If flag set, force all streams to normal priority
|
||||
// This means that GPU_MAX_HW_QUEUES may need to be incremented
|
||||
// to account for the additional normal priority queues
|
||||
if (DEBUG_HIP_IGNORE_STREAM_PRIORITY) {
|
||||
queue_priority = HSA_AMD_QUEUE_PRIORITY_NORMAL;
|
||||
qIndex = QueuePriority::Normal;
|
||||
}
|
||||
|
||||
// Else create a new queue. This also includes the initial state where there
|
||||
// is no queue.
|
||||
{ // Lock
|
||||
amd::ScopedLock l(active_queue_access_);
|
||||
|
||||
assert(queuePool_[QueuePriority::Low].size() <= settings().max_hw_queues_ ||
|
||||
queuePool_[QueuePriority::Normal].size() <= settings().max_hw_queues_ ||
|
||||
queuePool_[QueuePriority::High].size() <= settings().max_hw_queues_);
|
||||
|
||||
ClPrint(amd::LOG_INFO, amd::LOG_QUEUE,
|
||||
"Number of allocated hardware queues with low priority: %d,"
|
||||
" with normal priority: %d, with high priority: %d, maximum per priority is: %d",
|
||||
queuePool_[QueuePriority::Low].size(), queuePool_[QueuePriority::Normal].size(),
|
||||
queuePool_[QueuePriority::High].size(), settings().max_hw_queues_);
|
||||
|
||||
// If we have reached the max number of queues, reuse an existing queue with the matching queue
|
||||
// priority, choosing the one with the least number of users. Note: Don't attempt to reuse the
|
||||
// cooperative queue, since it's single per device.
|
||||
|
||||
// num_queues_[qIndex] tracks persistent (non-managed) queues per priority.
|
||||
// When the total queues (managed + non-managed) exceed max_hw_queues_, we must reuse existing
|
||||
// queues. 'managed' streams do not increment num_queues_, allowing them to use the
|
||||
// pool without permanently consuming slots. ReleaseActiveQueue() uses this counter to
|
||||
// decide when to start reclaiming queues.
|
||||
if (!coop_queue && (cuMask.size() == 0) &&
|
||||
(queuePool_[qIndex].size() >= settings().max_hw_queues_)) {
|
||||
hsa_queue_t* queue = getQueueFromPool(qIndex, false);
|
||||
if (queue != nullptr) {
|
||||
if (!managed) {
|
||||
num_queues_[qIndex]++;
|
||||
}
|
||||
return queue;
|
||||
}
|
||||
}
|
||||
} // Lock release
|
||||
|
||||
// Create a new queue.
|
||||
uint32_t queue_max_packets = 0;
|
||||
if (HSA_STATUS_SUCCESS !=
|
||||
Hsa::agent_get_info(bkendDevice_, HSA_AGENT_INFO_QUEUE_MAX_SIZE, &queue_max_packets)) {
|
||||
@@ -2971,9 +3021,14 @@ hsa_queue_t* Device::acquireQueue(uint32_t queue_size_hint, bool coop_queue,
|
||||
&queue) != HSA_STATUS_SUCCESS) {
|
||||
queue_size >>= 1;
|
||||
if (queue_size < 64) {
|
||||
// if a queue with the same requested priority available from the pool, returns it here
|
||||
if (!coop_queue && (cuMask.size() == 0) && (queuePool_[qIndex].size() > 0)) {
|
||||
return getQueueFromPool(qIndex);
|
||||
LogError("Device::acquireQueue: hsa_queue_create failed!");
|
||||
// If we can't create even a small queue, try to reuse any existing queue
|
||||
if (!coop_queue && (cuMask.size() == 0)) {
|
||||
amd::ScopedLock l(active_queue_access_);
|
||||
if (queuePool_[qIndex].size() > 0) {
|
||||
bool kForceReuse = true;
|
||||
return getQueueFromPool(qIndex, kForceReuse);
|
||||
}
|
||||
}
|
||||
ClPrint(amd::LOG_DETAIL_DEBUG, amd::LOG_QUEUE,
|
||||
"Device::acquireQueue: hsa_queue_create failed!");
|
||||
@@ -3067,12 +3122,14 @@ hsa_queue_t* Device::acquireQueue(uint32_t queue_size_hint, bool coop_queue,
|
||||
return nullptr;
|
||||
}
|
||||
if (cuMask.size() != 0) {
|
||||
amd::ScopedLock l(active_queue_access_);
|
||||
// add queues with custom CU mask into their special pool to keep track
|
||||
// of mapping of these queues to their associated queueInfo (i.e., hostcall buffers)
|
||||
auto result = queueWithCUMaskPool_[qIndex].emplace(std::make_pair(queue, QueueInfo()));
|
||||
assert(result.second && "QueueInfo already exists");
|
||||
auto& qInfo = result.first->second;
|
||||
qInfo.refCount = 1;
|
||||
qInfo.hasDedicatedQueue_ = dedicated_queue; // Track if this is a dedicated queue
|
||||
|
||||
return queue;
|
||||
}
|
||||
@@ -3083,22 +3140,41 @@ hsa_queue_t* Device::acquireQueue(uint32_t queue_size_hint, bool coop_queue,
|
||||
// per device.
|
||||
return queue;
|
||||
}
|
||||
|
||||
// Add queue to the pool (including dedicated queues)
|
||||
amd::ScopedLock l(active_queue_access_);
|
||||
auto result = queuePool_[qIndex].emplace(std::make_pair(queue, QueueInfo()));
|
||||
assert(result.second && "QueueInfo already exists");
|
||||
auto& qInfo = result.first->second;
|
||||
qInfo.refCount = 1;
|
||||
ClPrint(amd::LOG_INFO, amd::LOG_QUEUE, "acquireQueue refCount: %p (%d)",
|
||||
result.first->first->base_address, result.first->second.refCount);
|
||||
if (!managed && (cuMask.size() == 0) && (qIndex = QueuePriority::Normal)) {
|
||||
num_normal_queues_++;
|
||||
qInfo.hasDedicatedQueue_ = dedicated_queue; // Track if this is a dedicated queue
|
||||
ClPrint(amd::LOG_INFO, amd::LOG_QUEUE, "acquireQueue refCount: %p (%d) %s",
|
||||
result.first->first->base_address, result.first->second.refCount,
|
||||
dedicated_queue ? "(dedicated)" : "");
|
||||
if (!managed && (cuMask.size() == 0)) {
|
||||
num_queues_[qIndex]++;
|
||||
}
|
||||
return queue;
|
||||
}
|
||||
|
||||
// ================================================================================================
|
||||
bool Device::ReleaseActiveNormalQueue(hsa_queue_t* queue) {
|
||||
bool Device::ReleaseActiveQueue(hsa_queue_t* queue, amd::CommandQueue::Priority priority) {
|
||||
uint qIndex;
|
||||
switch (priority) {
|
||||
case amd::CommandQueue::Priority::Low:
|
||||
qIndex = QueuePriority::Low;
|
||||
break;
|
||||
case amd::CommandQueue::Priority::High:
|
||||
qIndex = QueuePriority::High;
|
||||
break;
|
||||
case amd::CommandQueue::Priority::Normal:
|
||||
case amd::CommandQueue::Priority::Medium:
|
||||
default:
|
||||
qIndex = QueuePriority::Normal;
|
||||
break;
|
||||
}
|
||||
// Release a queue if the total number of allocated queues exceeds the max possible
|
||||
if (num_normal_queues_.load() > GPU_MAX_HW_QUEUES) {
|
||||
if (num_queues_[qIndex].load() > settings().max_hw_queues_) {
|
||||
releaseQueue(queue, std::vector<uint32_t>{}, false, true);
|
||||
return true;
|
||||
} else {
|
||||
@@ -3109,36 +3185,52 @@ bool Device::ReleaseActiveNormalQueue(hsa_queue_t* queue) {
|
||||
// ================================================================================================
|
||||
void Device::releaseQueue(hsa_queue_t* queue, const std::vector<uint32_t>& cuMask, bool coop_queue,
|
||||
bool managed) {
|
||||
amd::ScopedLock l(active_queue_access_);
|
||||
for (auto& it : cuMask.size() == 0 ? queuePool_ : queueWithCUMaskPool_) {
|
||||
auto qIter = it.find(queue);
|
||||
if (qIter != it.end()) {
|
||||
if (!managed && (cuMask.size() == 0) && (&it == &queuePool_[QueuePriority::Normal])) {
|
||||
num_normal_queues_--;
|
||||
}
|
||||
auto& qInfo = qIter->second;
|
||||
assert(qInfo.refCount > 0);
|
||||
qInfo.refCount--;
|
||||
ClPrint(amd::LOG_INFO, amd::LOG_QUEUE, "releaseQueue refCount:%p (%d)",
|
||||
qIter->first->base_address, qIter->second.refCount);
|
||||
// hsa queues with cumask set are not being reused. Hence, if the app uses multiple
|
||||
// such queues it can cause memory leak and those must be destroyed here once the
|
||||
// refcount reaches 0.
|
||||
if ((!cuMask.empty()) && (qInfo.refCount == 0)) {
|
||||
if (qInfo.hostcallBuffer_) {
|
||||
ClPrint(amd::LOG_INFO, amd::LOG_QUEUE,
|
||||
"Deleting hostcall buffer %p for hardware queue %p", qInfo.hostcallBuffer_,
|
||||
qIter->first->base_address);
|
||||
amd::disableHostcalls(qInfo.hostcallBuffer_);
|
||||
context().svmFree(qInfo.hostcallBuffer_);
|
||||
// Defer cleanup operations outside the lock
|
||||
void* hostcallBufferToFree = nullptr;
|
||||
bool shouldDestroyQueue = false;
|
||||
|
||||
{ // Lock
|
||||
amd::ScopedLock l(active_queue_access_);
|
||||
auto& pools = cuMask.size() == 0 ? queuePool_ : queueWithCUMaskPool_;
|
||||
for (uint qIndex = 0; qIndex < pools.size(); ++qIndex) {
|
||||
auto& it = pools[qIndex];
|
||||
auto qIter = it.find(queue);
|
||||
if (qIter != it.end()) {
|
||||
if (!managed && (cuMask.size() == 0)) {
|
||||
num_queues_[qIndex]--;
|
||||
}
|
||||
ClPrint(amd::LOG_INFO, amd::LOG_QUEUE, "Deleting hardware queue %p with refCount 0",
|
||||
queue->base_address);
|
||||
qIter = it.erase(qIter);
|
||||
Hsa::queue_destroy(queue);
|
||||
auto& qInfo = qIter->second;
|
||||
assert(qInfo.refCount > 0);
|
||||
qInfo.refCount--;
|
||||
ClPrint(amd::LOG_INFO, amd::LOG_QUEUE, "releaseQueue refCount:%p (%d)",
|
||||
qIter->first->base_address, qIter->second.refCount);
|
||||
// hsa queues with cumask set are not being reused. Hence, if the app uses multiple
|
||||
// such queues it can cause memory leak and those must be destroyed here once the
|
||||
// refcount reaches 0.
|
||||
if ((!cuMask.empty()) && (qInfo.refCount == 0)) {
|
||||
hostcallBufferToFree = qInfo.hostcallBuffer_;
|
||||
shouldDestroyQueue = true;
|
||||
ClPrint(amd::LOG_INFO, amd::LOG_QUEUE, "Deleting hardware queue %p with refCount 0",
|
||||
queue->base_address);
|
||||
it.erase(qIter);
|
||||
}
|
||||
break; // Found and processed the queue
|
||||
}
|
||||
}
|
||||
} // Lock release
|
||||
|
||||
// Perform expensive cleanup operations outside the lock
|
||||
if (shouldDestroyQueue) {
|
||||
if (hostcallBufferToFree) {
|
||||
ClPrint(amd::LOG_INFO, amd::LOG_QUEUE,
|
||||
"Deleting hostcall buffer %p for hardware queue %p", hostcallBufferToFree,
|
||||
queue->base_address);
|
||||
amd::disableHostcalls(hostcallBufferToFree);
|
||||
context().svmFree(hostcallBufferToFree);
|
||||
}
|
||||
Hsa::queue_destroy(queue);
|
||||
}
|
||||
|
||||
if (coop_queue) { // cooperative queue
|
||||
ClPrint(amd::LOG_INFO, amd::LOG_QUEUE, "Deleting CG enabled hardware queue %p ",
|
||||
queue->base_address);
|
||||
@@ -3150,6 +3242,7 @@ void* Device::getOrCreateHostcallBuffer(hsa_queue_t* queue, bool coop_queue,
|
||||
const std::vector<uint32_t>& cuMask) {
|
||||
decltype(queuePool_)::value_type::iterator qIter;
|
||||
bool found = false;
|
||||
|
||||
if (!coop_queue) {
|
||||
for (auto& it : cuMask.size() == 0 ? queuePool_ : queueWithCUMaskPool_) {
|
||||
qIter = it.find(queue);
|
||||
|
||||
Reference in New Issue
Block a user