Skip to content

Device-wide chained scan with lookback - #1086

Open
keptsecret wants to merge 22 commits into
masterfrom
device_chained_scan
Open

Device-wide chained scan with lookback#1086
keptsecret wants to merge 22 commits into
masterfrom
device_chained_scan

Conversation

@keptsecret

Copy link
Copy Markdown
Contributor

No description provided.

return spirv::atomicSMin<T>(ptr, spv::ScopeDevice, spv::MemorySemanticsMaskNone, value);
}

template<typename Ptr_T> // DXC Workaround

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.

It's best to say exactly what dxc issue we're working around and be more detailed in the comments. in case they fix it and we can upgrade our code.
I think it's something to do with passing groupshared or other address qualifiers here.

I have a question though.
These don't need to be cpp compatible, it's gpu specific, why are we using NBL_REF_ARG and both REQ_TOP and REQ_BOT (hlsl enable_if and c++20 requires). I think BOT is enough?


Side note:
Just putting it out there that I don't like TOP/BOT naming 😆 I'd prefer something along the lines of:

  • NBL_HOST_CONCEPT + NBL_DEVICE_CONCEPT or NBL_DEVICE_SFINAE. which is more clear

void __call(NBL_REF_ARG(DataAccessor) dataAccessor, NBL_REF_ARG(ScratchAccessor) scratchAccessor, NBL_REF_ARG(ReductionAccessor) workgroupReduction, NBL_REF_ARG(WorkgroupCounter) workgroupCounter)
{
const uint16_t invocIx = workgroup::SubgroupContiguousIndex();
if (!invocIx)

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.

For readability purposes if you want to check if something is equal to zero, it's best to just do ==0 instead of treating as a boolean


uint16_t workgroupId;
scratchAccessor.template get<uint32_t, uint32_t>(0u, workgroupId);
scratchAccessor.workgroupExecutionAndMemoryBarrier();

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.

you don't need a workgroupExecutionAndMemoryBarrier here.

workgroup2::exclusive_scan<Config,BinOp,device_capabilities>::template __call<wg_data_proxy_t, ScratchAccessor>(wgDataAccessor, scratchAccessor);
else
workgroup2::inclusive_scan<Config,BinOp,device_capabilities>::template __call<wg_data_proxy_t, ScratchAccessor>(wgDataAccessor, scratchAccessor);
scratchAccessor.workgroupExecutionAndMemoryBarrier();

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 don't think you need a workgroupExecutionAndMemoryBarrier Because you want to access the preloaded array which is thread local.

Comment on lines +118 to +122
currGroupReduction = wgDataAccessor.preloaded[wg_data_proxy_t::PreloadedDataCount-1u][Config::ItemsPerInvocation_0-1u];
if (Exclusive)
currGroupReduction = binop(currGroupReduction, lastElem);
if (invocIx == lastInvocIx)
scratchAccessor.template set<scalar_t, uint32_t>(0u, currGroupReduction);

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.

You only need to do this for the last invocation.
So maybe you can encompass all of it under if (invocIx == lastInvocIx)

if (workgroupId)
{
bool locked = sIsLocked;
scratchAccessor.workgroupExecutionAndMemoryBarrier();

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.

why do workgroups need to sync here?

scratchAccessor.workgroupExecutionAndMemoryBarrier();

locked = sIsLocked;
scratchAccessor.workgroupExecutionAndMemoryBarrier();

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.

why workgroupExecutionAndMemoryBarrier here ?


locked = sIsLocked;
scratchAccessor.workgroupExecutionAndMemoryBarrier();
if (locked)

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.

Add comment:
Fall back path: we spun to MaxSpinCount But no previous workgroup had there global reduction ready (Flag_Inclusive).
So we try to do reduction for all previous work groups one by one until we reach Flag_Inclusive

const scalar_t storeVal = hlsl::mix(Flag_Inclusive, Flag_Reduction, fallbackGroupId > 0u) | (fallbackReduction << Flag_Shift);
const scalar_t fallbackPayload = workgroupReduction.atomicMax(fallbackGroupId, storeVal);

prevReduction = binop(prevReduction, hlsl::mix(fallbackReduction, fallbackPayload >> Flag_Shift, fallbackPayload > scalar_t(0.0)));

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 get why you're doing atomic Max here, You want to take the inclusive one if the original workgroup Finished after we calculated the reduction redundantly
But I don't get the mix here. Why not take the fall back payload all the time?

if (fallbackGroupId == 0u || (fallbackPayload & Flag_Mask) == Flag_Inclusive)
{
const scalar_t storeVal = Flag_Inclusive | (binop(prevReduction, currGroupReduction) << Flag_Shift);
workgroupReduction.atomicExchange(workgroupId, storeVal);

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.

Be careful again, you need some sort of memory semantics to ensure correct memory barriers when different work groups access the same value atomically.

scratchAccessor.workgroupExecutionAndMemoryBarrier();

locked = sIsLocked;
scratchAccessor.workgroupExecutionAndMemoryBarrier();

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.

why workgroupExecutionAndMemoryBarrier here ?

wg_data_proxy_t fallbackDataAccessor = wg_data_proxy_t::create(dataAccessor.getInputBufAddr(), dataAccessor.getOutputBufAddr(), fallbackGroupId);
fallbackDataAccessor.preload();
scalar_t fallbackReduction = workgroup2::reduction<Config,BinOp,device_capabilities>::template __call<wg_data_proxy_t, ScratchAccessor>(fallbackDataAccessor, scratchAccessor);
scratchAccessor.workgroupExecutionAndMemoryBarrier();

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.

why workgroupExecutionAndMemoryBarrier here ?

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.

2 participants