Thread Computation Examples
Block 0 - Thread 2 (Boundary)
Window: [3, 4, 5] (uses overlap)
Result: 3×0.33 + 4×0.33 + 5×0.33 = 4.0
Block 1 - Thread 3 (Boundary)
Window: [8, 9, 10] (uses overlap)
Result: 8×0.33 + 9×0.33 + 10×0.33 = 9.0
Block 2 - Thread 2 (Edge)
Window: [11, 12, 0] (zero padding)
Result: 11×0.33 + 12×0.33 + 0×0.33 = 7.67
Block 2 - Thread 3 (Edge)
Window: [12, 0, 0] (zero padding)
Result: 12×0.33 + 0×0.33 + 0×0.33 = 4.0
1D Convolution with Block Boundary Handling
Setup & Global Input Array
TPB: 4 | CONV_2: 3 | SIZE_2: 12
Indices:
0
1
2
3
4
5
6
7
8
9
10
11
Values:
1
2
3
4
5
6
7
8
9
10
11
12
Block 0: 0-3 | Block 1: 4-7 | Block 2: 8-11
Kernel: [0.33, 0.33, 0.33]
Function Signature
fn conv_1d_block_boundary[
in_layout: Layout,
out_layout: Layout,
conv_layout: Layout,
dtype: DType
](
output: LayoutTensor[mut=False, dtype, out_layout],
a: LayoutTensor[mut=False, dtype, in_layout],
b: LayoutTensor[mut=False, dtype, conv_layout],
):
Block 0: Shared Memory Loading
Step 1: Initialize Variables and Allocate Extended Shared Memory
global_i = block_dim.x * block_idx.x + thread_idx.x
local_i = thread_idx.x
shared_a = tb[dtype]().row_major[TPB + CONV_2 - 1]().shared().alloc()
shared_b = tb[dtype]().row_major[CONV_2]().shared().alloc()
Step 2: Load Main Data (Each Thread Loads One Element)
if global_i < SIZE_2:
shared_a[local_i] = a[global_i]
else:
shared_a[local_i] = 0
Shared Memory Layout After Main Loading:
■ Main Data Loaded
■ Overlap (to be loaded)
Step 3: Load Overlap Data (Boundary Threads Only)
if local_i < CONV_2 - 1:
next_idx = global_i + TPB
if next_idx < SIZE_2:
shared_a[TPB + local_i] = a[next_idx]
else:
shared_a[TPB + local_i] = 0
Complete Shared Memory for Block 0
Step 4: Load Kernel Data
if local_i < CONV_2:
shared_b[local_i] = b[local_i]
barrier()
Block 0: Convolution Computation
Convolution Algorithm
if global_i < SIZE_2:
var local_sum: output.element_type = 0
@parameter
for j in range(CONV_2):
if global_i + j < SIZE_2:
local_sum += shared_a[local_i + j] * shared_b[j]
output[global_i] = local_sum
Thread 0 (global_i=0, local_i=0)
Window: shared_a[0:3] = [1, 2, 3]
Computation:
1×0.33 + 2×0.33 + 3×0.33
Result: 2.0
Thread 1 (global_i=1, local_i=1)
Window: shared_a[1:4] = [2, 3, 4]
Computation:
2×0.33 + 3×0.33 + 4×0.33
Result: 3.0
Thread 2 (global_i=2, local_i=2) *
Window: shared_a[2:5] = [3, 4, 5]
Computation:
3×0.33 + 4×0.33 + 5×0.33
Result: 4.0
Uses overlap data!
Thread 3 (global_i=3, local_i=3) *
Window: shared_a[3:6] = [4, 5, 6]
Computation:
4×0.33 + 5×0.33 + 6×0.33
Result: 5.0
Uses overlap data!
Block 1: Shared Memory Loading
Block 1 Thread Mapping
block_idx.x = 1
block_dim.x = 4
Step 1: Load Main Data (Indices 4-7)
if global_i < SIZE_2:
shared_a[local_i] = a[global_i]
Shared Memory Layout for Block 1:
Step 2: Load Overlap Data from Block 2 (Indices 8-9)
if local_i < CONV_2 - 1:
next_idx = global_i + TPB
if next_idx < SIZE_2:
shared_a[TPB + local_i] = a[next_idx]
else:
shared_a[TPB + local_i] = 0
Complete Shared Memory for Block 1
Block 1: Convolution Computation
Global Memory Access by Block 1:
1
2
3
4
5
6
7
8
9
10
11
12
Thread 0 (global_i=4, local_i=0)
Window: shared_a[0:3] = [5, 6, 7]
Computation:
5×0.33 + 6×0.33 + 7×0.33
Result: 6.0
Thread 1 (global_i=5, local_i=1)
Window: shared_a[1:4] = [6, 7, 8]
Computation:
6×0.33 + 7×0.33 + 8×0.33
Result: 7.0
Thread 2 (global_i=6, local_i=2) *
Window: shared_a[2:5] = [7, 8, 9]
Computation:
7×0.33 + 8×0.33 + 9×0.33
Result: 8.0
Uses overlap data!
Thread 3 (global_i=7, local_i=3) *
Window: shared_a[3:6] = [8, 9, 10]
Computation:
8×0.33 + 9×0.33 + 10×0.33
Result: 9.0
Uses overlap data!
Block 2: Shared Memory Loading (Edge Case)
Block 2 Thread Mapping
block_idx.x = 2
block_dim.x = 4
Step 1: Load Main Data (Indices 8-11)
if global_i < SIZE_2:
shared_a[local_i] = a[global_i]
Shared Memory Layout for Block 2:
Step 2: Handle Out-of-Bounds Overlap (Zero Padding)
if local_i < CONV_2 - 1:
next_idx = global_i + TPB
if next_idx < SIZE_2:
shared_a[TPB + local_i] = a[next_idx]
else:
shared_a[TPB + local_i] = 0
Complete Shared Memory for Block 2 (with zero padding)
⚡ Block 2: Convolution Computation
Global Memory Access by Block 2:
1
2
3
4
5
6
7
8
9
10
11
12
⚠️ No overlap data available - using zero padding
Thread 0 (global_i=8, local_i=0)
Window: shared_a[0:3] = [9, 10, 11]
Computation:
9×0.33 + 10×0.33 + 11×0.33
Result: 10.0
Thread 1 (global_i=9, local_i=1)
Window: shared_a[1:4] = [10, 11, 12]
Computation:
10×0.33 + 11×0.33 + 12×0.33
Result: 11.0
Thread 2 (global_i=10, local_i=2) ⚠️
Window: shared_a[2:5] = [11, 12, 0]
Computation:
11×0.33 + 12×0.33 + 0×0.33
Result: 7.67
Uses zero padding!
Thread 3 (global_i=11, local_i=3) ⚠️
Window: shared_a[3:6] = [12, 0, 0]
Computation:
12×0.33 + 0×0.33 + 0×0.33
Result: 4.0
Uses zero padding!
📊 Complete Convolution Results
Input Array:
1
2
3
4
5
6
7
8
9
10
11
12
↓ CONVOLUTION ↓
Output Array:
2.0
3.0
4.0
5.0
6.0
7.0
8.0
9.0
10.0
11.0
7.67
4.0
■ Block 0 Results
■ Block 1 Results
■ Block 2 Results
Complete Implementation Summary
fn conv_1d_block_boundary[
in_layout: Layout,
out_layout: Layout,
conv_layout: Layout,
dtype: DType
](
output: LayoutTensor[mut=False, dtype, out_layout],
a: LayoutTensor[mut=False, dtype, in_layout],
b: LayoutTensor[mut=False, dtype, conv_layout],
):
global_i = block_dim.x * block_idx.x + thread_idx.x
local_i = thread_idx.x
shared_a = tb[dtype]().row_major[TPB + CONV_2 - 1]().shared().alloc()
shared_b = tb[dtype]().row_major[CONV_2]().shared().alloc()
if global_i < SIZE_2:
shared_a[local_i] = a[global_i]
else:
shared_a[local_i] = 0
if local_i < CONV_2 - 1:
next_idx = global_i + TPB
if next_idx < SIZE_2:
shared_a[TPB + local_i] = a[next_idx]
else:
shared_a[TPB + local_i] = 0
if local_i < CONV_2:
shared_b[local_i] = b[local_i]
barrier()
if global_i < SIZE_2:
var local_sum: output.element_type = 0
@parameter
for j in range(CONV_2):
if global_i + j < SIZE_2:
local_sum += shared_a[local_i + j] * shared_b[j]
output[global_i] = local_sum
Key Design Principles
Performance Benefits
• Memory Coalescing: Sequential access patterns
• Shared Memory: Fast on-chip storage
• Bandwidth Efficiency: Each element loaded once
• Parallel Execution: All threads work simultaneously
Critical Considerations
• Boundary Safety: Zero padding prevents crashes
• Edge Effects: Last block has artifacts
• Memory Size: TPB + CONV_2 - 1 allocation
• Synchronization: barrier() before computation