[llvm-dev] Implementing cross-thread reduction in the AMDGPU backend

Tom Stellard via llvm-dev llvm-dev at lists.llvm.org
Wed Jun 14 07:35:44 PDT 2017


On 06/14/2017 10:33 AM, Sumner, Brian wrote:
> Sorry about the formatting...
> 
> Anyway, I think there may be a misinterpretation of bound_cntl.  My understanding is that:
> 
> 0 => if the source is invalid or disabled, do not write a result
> 1 => if the source is invalid or disabled, use a 0 instead
> 
> So the problematic case is where bound_cntl is 0, not when it is 1.
> 

Yes, you are right, I had that mixed up.

-Tom

> -----Original Message-----
> From: Tom Stellard [mailto:tstellar at redhat.com] 
> Sent: Tuesday, June 13, 2017 6:13 PM
> To: Matt Arsenault
> Cc: Connor Abbott; llvm-dev at lists.llvm.org; Kolton, Sam; Sumner, Brian; Pykhtin, Valery
> Subject: Re: [llvm-dev] Implementing cross-thread reduction in the AMDGPU backend
> 
> On 06/13/2017 07:33 PM, Matt Arsenault wrote:
>>
>>> On Jun 12, 2017, at 17:23, Tom Stellard <tstellar at redhat.com <mailto:tstellar at redhat.com>> wrote:
>>>
>>> On 06/12/2017 08:03 PM, Connor Abbott wrote:
>>>> On Mon, Jun 12, 2017 at 4:56 PM, Tom Stellard <tstellar at redhat.com <mailto:tstellar at redhat.com>> wrote:
>>>>> On 06/12/2017 07:15 PM, Tom Stellard via llvm-dev wrote:
>>>>>> cc some people who have worked on this.
>>>>>>
>>>>>> On 06/12/2017 05:58 PM, Connor Abbott via llvm-dev wrote:
>>>>>>> Hi all,
>>>>>>>
>>>>>>> I've been looking into how to implement the more advanced Shader 
>>>>>>> Model
>>>>>>> 6 reduction operations in radv (and obviously most of the work 
>>>>>>> would be useful for radeonsi too). They're explained in the spec 
>>>>>>> for GL_AMD_shader_ballot at 
>>>>>>> https://www.khronos.org/registry/OpenGL/extensions/AMD/AMD_shader
>>>>>>> _ballot.txt, but I'll summarize them here. There are two types of 
>>>>>>> operations:
>>>>>>> reductions that always return a uniform value, and prefix scan 
>>>>>>> operations. The reductions can be implemented in terms of the 
>>>>>>> prefix scan (although in practice I don't think we want to 
>>>>>>> implement them in exactly the same way), and the concerns are 
>>>>>>> mostly the same, so I'll focus on the prefix scan operations for now. Given an operation `op'
>>>>>>> and an input value `a' (that's really a SIMD array with one value 
>>>>>>> per invocation, even though it's a scalar value in LLVM), the 
>>>>>>> prefix scan returns a[0] in invocation 0, a[0] `op' a[1] in 
>>>>>>> invocation 1, a[0] `op' a[1] `op' a[2] in invocation 2, etc. The 
>>>>>>> prefix scan will also work for non-uniform control flow: it 
>>>>>>> simply skips inactive invocations.
>>>>>>>
>>>>>>> On the LLVM side, I think that we have most of the AMD-specific 
>>>>>>> low-level shuffle intrinsics implemented that you need to do 
>>>>>>> this, but I can think of a few concerns/questions. First of all, 
>>>>>>> to implement the prefix scan, we'll need to do a code sequence 
>>>>>>> that looks like this, modified from 
>>>>>>> http://gpuopen.com/amd-gcn-assembly-cross-lane-operations/ 
>>>>>>> (replace
>>>>>>> v_foo_f32 with the appropriate operation):
>>>>>>>
>>>>>>> ; v0 is the input register
>>>>>>> v_mov_b32 v1, v0
>>>>>>> v_foo_f32 v1, v0, v1 row_shr:1 // Instruction 1
>>>>>>> v_foo_f32 v1, v0, v1 row_shr:2 // Instruction 2
>>>>>>> v_foo_f32 v1, v0, v1 row_shr:3/ / Instruction 3 v_nop // Add two 
>>>>>>> independent instructions to avoid a data hazard v_nop
>>>>>>> v_foo_f32 v1, v1, v1 row_shr:4 bank_mask:0xe // Instruction 4 
>>>>>>> v_nop // Add two independent instructions to avoid a data hazard 
>>>>>>> v_nop
>>>>>>> v_foo_f32 v1, v1, v1 row_shr:8 bank_mask:0xc // Instruction 5 
>>>>>>> v_nop // Add two independent instructions to avoid a data hazard 
>>>>>>> v_nop
>>>>>>> v_foo_f32 v1, v1, v1 row_bcast:15 row_mask:0xa // Instruction 6 
>>>>>>> v_nop // Add two independent instructions to avoid a data hazard 
>>>>>>> v_nop
>>>>>>> v_foo_f32 v1, v1, v1 row_bcast:31 row_mask:0xc // Instruction 7
>>>>>>>
>>>>>>> The problem is that the way these instructions use the DPP word 
>>>>>>> isn't currently expressible in LLVM. We have the 
>>>>>>> llvm.amdgcn.mov_dpp intrinsic, but it isn't enough. For example, 
>>>>>>> take the first
>>>>>>> instruction:
>>>>>>>
>>>>>>> v_foo_f32 v1, v0, v1 row_shr:1
>>>>>>>
>>>>>>> What it's doing is shifting v0 right by one within each row and 
>>>>>>> adding it to v1. v1 stays the same in the first lane of each row, however.
>>>>>>> With llvm.amdgcn.mov_dpp, we could try to express it as something 
>>>>>>> like this, in LLVM-like pseduocode:
>>>>>>>
>>>>>>> %tmp = call llvm.amdgcn.mov_dpp %input row_shr:1 %result = foo 
>>>>>>> %tmp, %input
>>>>>>>
>>>>>>> but this is incorrect. If I'm reading the source correctly, this 
>>>>>>> will make %tmp garbage in lane 0 (since it just turns into a 
>>>>>>> normal move with the dpp modifier, and no restrictions on the 
>>>>>>> destination). We could set bound_ctrl to 0 to work around this, 
>>>>>>> since it will make %tmp
>>>>>>> 0 in lane 0, but that won't work with operations whose identity 
>>>>>>> is
>>>>>>> non-0 like min and max. What we need is something like:
>>>>>>>
>>>>>
>>>>> Why is %tmp garbage?  I thought the two options were 0 (bound_ctrl 
>>>>> =0) or %input (bound_ctrl = 1)?
>>>>
>>>> Oh, maybe it is... for that to happen the underlying move would need 
>>>> to have the source and destination constrained to be the same. I 
>>>> couldn't see that constraint anywhere I looked, but I'm not an 
>>>> expert, so I may have overlooked it. In any case, that behavior 
>>>> still isn't what we want if we want to implement the prefix scan 
>>>> operations efficiently.
>>>>
>>>
>>> Ok, I see what you are saying now.  I think the best option here is 
>>> to document that the behavior of the llvm.amdgcn.mov.dpp intrinsic is 
>>> to copy its src operand to dst when bound_ctrl = 1 and it reads from 
>>> an invalid thread, and then when bound_ctrl=1, lower the intrinsic to 
>>> a special tied version of V_MOV_B32_dpp where the src and dst are the same register.
>>>
>>> -Tom
>>
>> This came up before that there’s no way to represent the unmodified input register for the inactive lanes. I think the conclusion was that a new intrinsic is needed to represent this case but I don’t think there was a consensus on what it should look like.
>>
> 
> I think re-defining the existing intrinsic will be easier than adding a new one.  Also, if we add a new one, llvm.amdgcn.mov.dpp will still be broken for the bound_ctrl=1 case, which isn't really ideal.
> 
> -Tom
> 
>> -Matt
>>
> 



More information about the llvm-dev mailing list