Repository: JuliaGPU/KernelAbstractions.jl
Pull request #563 (2025-02-07 11:31:29)

Allow opt-out of implicit bounds-checking
KernelAbstractions currently creates kernels that look like:

```
if __validindex(ctx)
   # Body
end
```

This is problematic due to the convergence requirement on
`@synchronize`.

vchuravy: > [!WARNING]
> <b>This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack <a href="https://app.graphite.dev/github/pr/JuliaGPU/KernelAbstractions.jl/563?utm_source=stack-comment-downstack-mergeability-warning" >on Graphite</a>.</b>
> <a href="https://graphite.dev/docs/merge-pull-requests">Learn more</a>

* **#562** <a href="https://app.graphite.dev/github/pr/JuliaGPU/KernelAbstractions.jl/562?utm_source=stack-comment-icon" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a>
* **#563** <a href="https://app.graphite.dev/github/pr/JuliaGPU/KernelAbstractions.jl/563?utm_source=stack-comment-icon" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a> 👈 <a href="https://app.graphite.dev/github/pr/JuliaGPU/KernelAbstractions.jl/563?utm_source=stack-comment-view-in-graphite" target="_blank">(View in Graphite)</a>
* **#556** <a href="https://app.graphite.dev/github/pr/JuliaGPU/KernelAbstractions.jl/556?utm_source=stack-comment-icon" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a>
* `main`


<h2></h2>

This stack of pull requests is managed by <a href="https://graphite.dev?utm-source=stack-comment"><b>Graphite</b></a>. Learn more about <a href="https://stacking.dev/?utm_source=stack-comment">stacking</a>.
<!-- Current dependencies on/for this PR: -->
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/JuliaGPU/KernelAbstractions.jl/pull/563?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=JuliaGPU) Report
Attention: Patch coverage is `0%` with `10 lines` in your changes missing coverage. Please review.
> Project coverage is 0.00%. Comparing base [(`a6ae55b`)](https://app.codecov.io/gh/JuliaGPU/KernelAbstractions.jl/commit/a6ae55b16fac27dbf17960f89fbabd1734c7864e?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=JuliaGPU) to head [(`48e3752`)](https://app.codecov.io/gh/JuliaGPU/KernelAbstractions.jl/commit/48e375288529410653f961c0c7049f31bcfac5df?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=JuliaGPU).

| [Files with missing lines](https://app.codecov.io/gh/JuliaGPU/KernelAbstractions.jl/pull/563?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=JuliaGPU) | Patch % | Lines |
|---|---|---|
| [src/macros.jl](https://app.codecov.io/gh/JuliaGPU/KernelAbstractions.jl/pull/563?src=pr&el=tree&filepath=src%2Fmacros.jl&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=JuliaGPU#diff-c3JjL21hY3Jvcy5qbA==) | 0.00% | [10 Missing :warning: ](https://app.codecov.io/gh/JuliaGPU/KernelAbstractions.jl/pull/563?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=JuliaGPU) |

<details><summary>Additional details and impacted files</summary>


```diff
@@           Coverage Diff           @@
##           vc/pocl    #563   +/-   ##
=======================================
  Coverage     0.00%   0.00%           
=======================================
  Files           21      21           
  Lines         1502    1506    +4     
=======================================
- Misses        1502    1506    +4     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/JuliaGPU/KernelAbstractions.jl/pull/563?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=JuliaGPU).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=JuliaGPU).

github-actions[bot]: ### Benchmark Results


|                                                | main                | 48e37528852941... | main/48e37528852941... |
|:-----------------------------------------------|:-------------------:|:-----------------:|:----------------------:|
| saxpy/default/Float16/1024                     | 0.74 ± 0.0069 μs    | 0.0571 ± 0.026 ms | 0.013                  |
| saxpy/default/Float16/1048576                  | 0.178 ± 0.0091 ms   | 0.89 ± 0.025 ms   | 0.2                    |
| saxpy/default/Float16/16384                    | 3.34 ± 0.043 μs     | 0.0634 ± 0.027 ms | 0.0527                 |
| saxpy/default/Float16/2048                     | 0.916 ± 0.01 μs     | 0.0536 ± 0.024 ms | 0.0171                 |
| saxpy/default/Float16/256                      | 0.595 ± 0.0061 μs   | 0.0565 ± 0.026 ms | 0.0105                 |
| saxpy/default/Float16/262144                   | 0.0442 ± 0.00079 ms | 0.272 ± 0.025 ms  | 0.163                  |
| saxpy/default/Float16/32768                    | 6.03 ± 0.1 μs       | 0.0745 ± 0.027 ms | 0.0809                 |
| saxpy/default/Float16/4096                     | 1.31 ± 0.021 μs     | 0.0597 ± 0.025 ms | 0.022                  |
| saxpy/default/Float16/512                      | 0.654 ± 0.0073 μs   | 0.055 ± 0.026 ms  | 0.0119                 |
| saxpy/default/Float16/64                       | 0.565 ± 0.0054 μs   | 0.0588 ± 0.027 ms | 0.00961                |
| saxpy/default/Float16/65536                    | 11.7 ± 0.18 μs      | 0.104 ± 0.027 ms  | 0.112                  |
| saxpy/default/Float32/1024                     | 0.663 ± 0.009 μs    | 0.0588 ± 0.026 ms | 0.0113                 |
| saxpy/default/Float32/1048576                  | 0.193 ± 0.029 ms    | 0.481 ± 0.042 ms  | 0.402                  |
| saxpy/default/Float32/16384                    | 3.3 ± 0.87 μs       | 0.0553 ± 0.025 ms | 0.0596                 |
| saxpy/default/Float32/2048                     | 0.784 ± 0.02 μs     | 0.0484 ± 0.023 ms | 0.0162                 |
| saxpy/default/Float32/256                      | 0.589 ± 0.0064 μs   | 0.0563 ± 0.027 ms | 0.0105                 |
| saxpy/default/Float32/262144                   | 0.0452 ± 0.0038 ms  | 0.161 ± 0.034 ms  | 0.281                  |
| saxpy/default/Float32/32768                    | 6.02 ± 1.2 μs       | 0.0611 ± 0.027 ms | 0.0986                 |
| saxpy/default/Float32/4096                     | 1.14 ± 0.071 μs     | 0.0528 ± 0.024 ms | 0.0216                 |
| saxpy/default/Float32/512                      | 0.624 ± 0.0081 μs   | 0.0557 ± 0.027 ms | 0.0112                 |
| saxpy/default/Float32/64                       | 0.575 ± 0.0072 μs   | 0.0599 ± 0.028 ms | 0.0096                 |
| saxpy/default/Float32/65536                    | 12.8 ± 1.3 μs       | 0.0744 ± 0.029 ms | 0.172                  |
| saxpy/default/Float64/1024                     | 0.763 ± 0.029 μs    | 0.0563 ± 0.026 ms | 0.0136                 |
| saxpy/default/Float64/1048576                  | 0.481 ± 0.052 ms    | 0.503 ± 0.046 ms  | 0.956                  |
| saxpy/default/Float64/16384                    | 5.9 ± 1.1 μs        | 0.0559 ± 0.025 ms | 0.106                  |
| saxpy/default/Float64/2048                     | 1.14 ± 0.093 μs     | 0.0501 ± 0.023 ms | 0.0227                 |
| saxpy/default/Float64/256                      | 0.587 ± 0.0062 μs   | 0.0534 ± 0.027 ms | 0.011                  |
| saxpy/default/Float64/262144                   | 0.0904 ± 0.011 ms   | 0.171 ± 0.03 ms   | 0.528                  |
| saxpy/default/Float64/32768                    | 12.6 ± 1.4 μs       | 0.0637 ± 0.026 ms | 0.198                  |
| saxpy/default/Float64/4096                     | 1.81 ± 0.27 μs      | 0.0528 ± 0.024 ms | 0.0343                 |
| saxpy/default/Float64/512                      | 0.642 ± 0.0091 μs   | 0.0562 ± 0.027 ms | 0.0114                 |
| saxpy/default/Float64/64                       | 0.563 ± 0.0055 μs   | 0.0551 ± 0.028 ms | 0.0102                 |
| saxpy/default/Float64/65536                    | 24 ± 2.3 μs         | 0.0857 ± 0.026 ms | 0.28                   |
| saxpy/static workgroup=(1024,)/Float16/1024    | 2.18 ± 0.025 μs     | 0.0497 ± 0.025 ms | 0.0438                 |
| saxpy/static workgroup=(1024,)/Float16/1048576 | 0.158 ± 0.0078 ms   | 0.901 ± 0.03 ms   | 0.175                  |
| saxpy/static workgroup=(1024,)/Float16/16384   | 4.42 ± 0.083 μs     | 0.0598 ± 0.025 ms | 0.0739                 |
| saxpy/static workgroup=(1024,)/Float16/2048    | 2.34 ± 0.026 μs     | 0.0569 ± 0.024 ms | 0.0412                 |
| saxpy/static workgroup=(1024,)/Float16/256     | 2.83 ± 0.032 μs     | 0.0541 ± 0.026 ms | 0.0524                 |
| saxpy/static workgroup=(1024,)/Float16/262144  | 0.0426 ± 0.0015 ms  | 0.267 ± 0.027 ms  | 0.16                   |
| saxpy/static workgroup=(1024,)/Float16/32768   | 6.85 ± 0.2 μs       | 0.0727 ± 0.025 ms | 0.0942                 |
| saxpy/static workgroup=(1024,)/Float16/4096    | 2.67 ± 0.033 μs     | 0.0505 ± 0.026 ms | 0.0528                 |
| saxpy/static workgroup=(1024,)/Float16/512     | 3.28 ± 0.035 μs     | 0.0504 ± 0.025 ms | 0.065                  |
| saxpy/static workgroup=(1024,)/Float16/64      | 2.54 ± 0.21 μs      | 0.0559 ± 0.026 ms | 0.0454                 |
| saxpy/static workgroup=(1024,)/Float16/65536   | 12.7 ± 0.44 μs      | 0.101 ± 0.025 ms  | 0.126                  |
| saxpy/static workgroup=(1024,)/Float32/1024    | 2.25 ± 0.037 μs     | 0.0552 ± 0.026 ms | 0.0408                 |
| saxpy/static workgroup=(1024,)/Float32/1048576 | 0.212 ± 0.027 ms    | 0.462 ± 0.039 ms  | 0.458                  |
| saxpy/static workgroup=(1024,)/Float32/16384   | 4.39 ± 0.38 μs      | 0.0522 ± 0.024 ms | 0.0841                 |
| saxpy/static workgroup=(1024,)/Float32/2048    | 2.39 ± 0.049 μs     | 0.0495 ± 0.023 ms | 0.0483                 |
| saxpy/static workgroup=(1024,)/Float32/256     | 2.7 ± 0.046 μs      | 0.0525 ± 0.026 ms | 0.0514                 |
| saxpy/static workgroup=(1024,)/Float32/262144  | 0.05 ± 0.0049 ms    | 0.157 ± 0.035 ms  | 0.319                  |
| saxpy/static workgroup=(1024,)/Float32/32768   | 7.62 ± 0.71 μs      | 0.0576 ± 0.025 ms | 0.132                  |
| saxpy/static workgroup=(1024,)/Float32/4096    | 2.67 ± 0.081 μs     | 0.0492 ± 0.026 ms | 0.0543                 |
| saxpy/static workgroup=(1024,)/Float32/512     | 2.71 ± 0.034 μs     | 0.0552 ± 0.026 ms | 0.0491                 |
| saxpy/static workgroup=(1024,)/Float32/64      | 2.74 ± 5.8 μs       | 0.0548 ± 0.026 ms | 0.05                   |
| saxpy/static workgroup=(1024,)/Float32/65536   | 14.8 ± 1.6 μs       | 0.0718 ± 0.028 ms | 0.207                  |
| saxpy/static workgroup=(1024,)/Float64/1024    | 2.31 ± 0.061 μs     | 0.0506 ± 0.025 ms | 0.0455                 |
| saxpy/static workgroup=(1024,)/Float64/1048576 | 0.494 ± 0.049 ms    | 0.5 ± 0.044 ms    | 0.989                  |
| saxpy/static workgroup=(1024,)/Float64/16384   | 7.52 ± 0.74 μs      | 0.0544 ± 0.025 ms | 0.138                  |
| saxpy/static workgroup=(1024,)/Float64/2048    | 2.59 ± 0.08 μs      | 0.0491 ± 0.023 ms | 0.0527                 |
| saxpy/static workgroup=(1024,)/Float64/256     | 2.66 ± 0.057 μs     | 0.0551 ± 0.026 ms | 0.0482                 |
| saxpy/static workgroup=(1024,)/Float64/262144  | 0.0996 ± 0.013 ms   | 0.171 ± 0.031 ms  | 0.584                  |
| saxpy/static workgroup=(1024,)/Float64/32768   | 14.7 ± 1.6 μs       | 0.0622 ± 0.025 ms | 0.237                  |
| saxpy/static workgroup=(1024,)/Float64/4096    | 3.15 ± 0.21 μs      | 0.0488 ± 0.025 ms | 0.0646                 |
| saxpy/static workgroup=(1024,)/Float64/512     | 2.67 ± 0.066 μs     | 0.051 ± 0.026 ms  | 0.0524                 |
| saxpy/static workgroup=(1024,)/Float64/64      | 2.61 ± 0.066 μs     | 0.0544 ± 0.026 ms | 0.048                  |
| saxpy/static workgroup=(1024,)/Float64/65536   | 26.7 ± 2.8 μs       | 0.0831 ± 0.027 ms | 0.322                  |
| time_to_load                                   | 0.316 ± 0.0066 s    | 1.11 ± 0.0056 s   | 0.286                  |



### Benchmark Plots
A plot of the benchmark results have been uploaded as an artifact to the workflow run for this PR.
Go to "Actions"->"Benchmark a pull request"->[the most recent run]->"Artifacts" (at the bottom).

vchuravy: ### Merge activity

* **Feb 13, 12:53 PM EST**: A user started a stack merge that includes this pull request via [Graphite](https://app.graphite.dev/github/pr/JuliaGPU/KernelAbstractions.jl/563).


Patch:
diff --git a/docs/src/index.md b/docs/src/index.md
index dbf90305..13b4a869 100644
--- a/docs/src/index.md
+++ b/docs/src/index.md
@@ -38,6 +38,87 @@ Major refactor of KernelAbstractions. In particular:
 - Removal of the event system. Kernel are now implicitly ordered.
 - Removal of backend packages, backends are now directly provided by CUDA.jl and similar
 
+#### 0.9.33
+Restricts the semantics of `@synchronize` to require convergent execution.
+The OpenCL backend had several miss-compilations due to divergent execution of `@synchronize`.
+The `CPU` backend always had this limitation and upon investigation the CUDA backend similarly requires convergent execution,
+but allows for a wider set of valid kernels.
+
+This highlighted a design flaw in KernelAbstractions. Most GPU implementations execute KernelAbstraction workgroups on static blocks
+This means a kernel with `ndrange=(32, 30)` might be executed on a static block of `(32,32)`. In order to block these extra indicies,
+KernelAbstraction would insert a dynamic boundscheck.
+
+Prior to v0.9.33 a kernel like
+
+```julia
+@kernel function localmem(A)
+    N = @uniform prod(@groupsize())
+    I = @index(Global, Linear)
+    i = @index(Local, Linear)
+    lmem = @localmem Int (N,) # Ok iff groupsize is static
+    lmem[i] = i
+    @synchronize
+    A[I] = lmem[N - i + 1]
+end
+```
+
+was lowered to GPU backends like this:
+
+```julia
+function localmem_gpu(A)
+    if __validindex(__ctx__)
+        N = @uniform prod(@groupsize())
+        I = @index(Global, Linear)
+        i = @index(Local, Linear)
+        lmem = @localmem Int (N,) # Ok iff groupsize is static
+        lmem[i] = i
+        @synchronize
+        A[I] = lmem[N - i + 1]
+    end
+end
+```
+
+This would cause an implicit divergent execution of `@synchronize`. 
+
+With this release the lowering has been changed to:
+
+```julia
+function localmem_gpu(A)
+    __valid_lane__ __validindex(__ctx__)
+    N = @uniform prod(@groupsize())
+    lmem = @localmem Int (N,) # Ok iff groupsize is static
+    if __valid_lane__
+        I = @index(Global, Linear)
+        i = @index(Local, Linear)
+        lmem[i] = i
+    end
+    @synchronize
+    if __valid_lane__
+        A[I] = lmem[N - i + 1]
+    end
+end
+```
+
+Note that this follow the CPU lowering with respect to `@uniform`, `@private`, `@localmem` and `@synchronize`.
+
+Since this transformation can be disruptive, user can now opt out of the implicit bounds-check,
+but users must avoid the use of `@index(Global)` and instead use their own derivation based on `@index(Group)` and `@index(Local)`.
+
+```julia
+@kernel unsafe_indicies=false function localmem(A)
+    N = @uniform prod(@groupsize())
+    gI = @index(Group, Linear)
+    i = @index(Local, Linear)
+    lmem = @localmem Int (N,) # Ok iff groupsize is static
+    lmem[i] = i
+    @synchronize
+    I = (gI - 1) * N + i
+    if i <= N && I <= length(A)
+        A[I] = lmem[N - i + 1]
+    end
+end
+```
+
 ## Semantic differences
 
 ### To CUDA.jl/AMDGPU.jl
diff --git a/src/KernelAbstractions.jl b/src/KernelAbstractions.jl
index 430718a3..f6292192 100644
--- a/src/KernelAbstractions.jl
+++ b/src/KernelAbstractions.jl
@@ -50,7 +50,7 @@ synchronize(backend)
 ```
 """
 macro kernel(expr)
-    return __kernel(expr, #=generate_cpu=# true, #=force_inbounds=# false)
+    return __kernel(expr, #=generate_cpu=# true, #=force_inbounds=# false, #=unsafe_indicies=# false)
 end
 
 """
@@ -60,6 +60,7 @@ This allows for two different configurations:
 
 1. `cpu={true, false}`: Disables code-generation of the CPU function. This relaxes semantics such that KernelAbstractions primitives can be used in non-kernel functions.
 2. `inbounds={false, true}`: Enables a forced `@inbounds` macro around the function definition in the case the user is using too many `@inbounds` already in their kernel. Note that this can lead to incorrect results, crashes, etc and is fundamentally unsafe. Be careful!
+3. `unsafe_indicies={false, true}`: Disables the implicit validation of indicies, users must avoid `@index(Global)`.
 
 - [`@context`](@ref)
 
@@ -68,9 +69,10 @@ This allows for two different configurations:
 """
 macro kernel(ex...)
     if length(ex) == 1
-        return __kernel(ex[1], true, false)
+        return __kernel(ex[1], true, false, false)
     else
         generate_cpu = true
+        unsafe_indicies = false
         force_inbounds = false
         for i in 1:(length(ex) - 1)
             if ex[i] isa Expr && ex[i].head == :(=) &&
@@ -79,16 +81,20 @@ macro kernel(ex...)
             elseif ex[i] isa Expr && ex[i].head == :(=) &&
                     ex[i].args[1] == :inbounds && ex[i].args[2] isa Bool
                 force_inbounds = ex[i].args[2]
+            elseif ex[i] isa Expr && ex[i].head == :(=) &&
+                    ex[i].args[1] == :unsafe_indicies && ex[i].args[2] isa Bool
+                unsafe_indicies = ex[i].args[2]
             else
                 error(
                     "Configuration should be of form:\n" *
                         "* `cpu=true`\n" *
                         "* `inbounds=false`\n" *
+                        "* `unsafe_indicies=false`\n" *
                         "got `", ex[i], "`",
                 )
             end
         end
-        return __kernel(ex[end], generate_cpu, force_inbounds)
+        return __kernel(ex[end], generate_cpu, force_inbounds, unsafe_indicies)
     end
 end
 
diff --git a/src/macros.jl b/src/macros.jl
index 0067cb2c..c70f7b96 100644
--- a/src/macros.jl
+++ b/src/macros.jl
@@ -10,7 +10,7 @@ function find_return(stmt)
 end
 
 # XXX: Proper errors
-function __kernel(expr, generate_cpu = true, force_inbounds = false)
+function __kernel(expr, generate_cpu = true, force_inbounds = false, unsafe_indicies = true)
     def = splitdef(expr)
     name = def[:name]
     args = def[:args]
@@ -46,7 +46,7 @@ function __kernel(expr, generate_cpu = true, force_inbounds = false)
 
     def_gpu = deepcopy(def)
     def_gpu[:name] = gpu_name = Symbol(:gpu_, name)
-    transform_gpu!(def_gpu, constargs, force_inbounds)
+    transform_gpu!(def_gpu, constargs, force_inbounds, unsafe_indicies)
     gpu_function = combinedef(def_gpu)
 
     # create constructor functions
@@ -78,7 +78,7 @@ end
 
 # The easy case, transform the function for GPU execution
 # - mark constant arguments by applying `constify`.
-function transform_gpu!(def, constargs, force_inbounds)
+function transform_gpu!(def, constargs, force_inbounds, unsafe_indicies)
     let_constargs = Expr[]
     for (i, arg) in enumerate(def[:args])
         if constargs[i]
@@ -94,7 +94,11 @@ function transform_gpu!(def, constargs, force_inbounds)
     if force_inbounds
         push!(new_stmts, Expr(:inbounds, true))
     end
-    append!(new_stmts, split(emit_gpu, body.args))
+    if !unsafe_indicies
+        append!(new_stmts, split(emit_gpu, body.args))
+    else
+        push!(new_stmts, body)
+    end
     if force_inbounds
         push!(new_stmts, Expr(:inbounds, :pop))
     end


Test patch:
diff --git a/test/localmem.jl b/test/localmem.jl
index a31235d0..a2904e31 100644
--- a/test/localmem.jl
+++ b/test/localmem.jl
@@ -34,9 +34,22 @@ end
     end
 end
 
+@kernel unsafe_indicies = false function localmem_unsafe_indicies(A)
+    N = @uniform prod(@groupsize())
+    gI = @index(Group, Linear)
+    i = @index(Local, Linear)
+    lmem = @localmem Int (N,) # Ok iff groupsize is static
+    lmem[i] = i
+    @synchronize
+    I = (gI - 1) * N + i
+    if I <= length(A)
+        A[I] = lmem[N - i + 1]
+    end
+end
+
 function localmem_testsuite(backend, ArrayT)
     @testset "kernels" begin
-        @testset for kernel! in (localmem(backend(), 16), localmem2(backend(), 16))
+        @testset for kernel! in (localmem(backend(), 16), localmem2(backend(), 16), localmem_unsafe_indicies(backend(), 16))
             A = ArrayT{Int}(undef, 64)
             kernel!(A, ndrange = size(A))
             synchronize(backend())