Skip to content

Commit 31551f1

Browse files
authored
Merge pull request #15 from denix56/triton
Triton
2 parents a5e7d09 + 2a89407 commit 31551f1

5 files changed

Lines changed: 73 additions & 151 deletions

File tree

benchmarks/test_benchmark_knn.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
(8192, 4096),
2727
(8192, 8192),
2828
(8201, 4103),
29+
(32000, 32000),
2930
(255, 127),
3031
(256, 5),
3132
(1024, 5),

torch_cluster/triton/_kernels.py

Lines changed: 60 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,13 @@
77

88

99
def _prune_knn_configs(configs, args, **kwargs):
10-
d = int(args.get('D', 0))
11-
max_cand = int(args.get('MAX_CAND', 0))
10+
k_pad = int(args.get("K", 0))
1211
pruned = []
1312
for cfg in configs:
14-
block_n = cfg.kwargs.get('BLOCK_N', 0)
15-
block_d = cfg.kwargs.get('BLOCK_D', 0)
16-
if max_cand > 0 and block_n > max_cand:
17-
continue
18-
if d > 0 and block_d > d:
13+
block_n = cfg.kwargs.get("BLOCK_N", 0)
14+
if block_n < k_pad:
1915
continue
2016
pruned.append(cfg)
21-
if not pruned:
22-
pruned = [min(configs, key=lambda c: c.kwargs.get('BLOCK_N', 0))]
2317
return pruned
2418

2519

@@ -79,6 +73,7 @@ def _nearest_kernel(
7973
USE_BATCH: tl.constexpr,
8074
COSINE: tl.constexpr,
8175
EPS: tl.constexpr,
76+
INPUT_PRECISION: tl.constexpr,
8277
BLOCK_M: tl.constexpr,
8378
BLOCK_N: tl.constexpr,
8479
BLOCK_K: tl.constexpr,
@@ -135,7 +130,6 @@ def _nearest_kernel(
135130
right = M
136131

137132
x_sq = tl.zeros((BLOCK_N,), dtype=tl.float32) # ||x||^2.
138-
x_sq_c = tl.zeros((BLOCK_N,), dtype=tl.float32)
139133
x_block_ptr_sq = tl.make_block_ptr(
140134
base=x_ptr,
141135
shape=(D, N),
@@ -153,11 +147,7 @@ def _nearest_kernel(
153147
boundary_check=(0, 1),
154148
padding_option="zero",
155149
)
156-
x_term = tl.sum(x * x, axis=0)
157-
x_y = x_term - x_sq_c
158-
x_t = x_sq + x_y
159-
x_sq_c = (x_t - x_sq) - x_y
160-
x_sq = x_t
150+
x_sq += tl.sum(x * x, axis=0)
161151
x_block_ptr_sq = tl.advance(x_block_ptr_sq, (BLOCK_K, 0))
162152
if COSINE:
163153
inv_x = tl.rsqrt(x_sq + EPS) # 1/||x||.
@@ -177,9 +167,7 @@ def _nearest_kernel(
177167
full_y = y_start + BLOCK_M <= M # Full tile.
178168

179169
acc = tl.zeros((BLOCK_M, BLOCK_N), dtype=tl.float32) # Dot acc.
180-
acc_c = tl.zeros((BLOCK_M, BLOCK_N), dtype=tl.float32)
181170
y_sq = tl.zeros((BLOCK_M,), dtype=tl.float32) # ||y||^2.
182-
y_sq_c = tl.zeros((BLOCK_M,), dtype=tl.float32)
183171

184172
x_block_ptr = x_block_ptr_base
185173
y_block_ptr = tl.make_block_ptr(
@@ -211,16 +199,8 @@ def _nearest_kernel(
211199
boundary_check=(0, 1),
212200
padding_option="zero",
213201
)
214-
dot_term = tl.dot(y, x, input_precision="ieee")
215-
dot_y = dot_term - acc_c
216-
dot_t = acc + dot_y
217-
acc_c = (dot_t - acc) - dot_y
218-
acc = dot_t
219-
y_term = tl.sum(y * y, axis=1)
220-
y_y = y_term - y_sq_c
221-
y_t = y_sq + y_y
222-
y_sq_c = (y_t - y_sq) - y_y
223-
y_sq = y_t
202+
acc += tl.dot(y, x, input_precision=INPUT_PRECISION)
203+
y_sq += tl.sum(y * y, axis=1)
224204
y_block_ptr = tl.advance(y_block_ptr, (0, BLOCK_K))
225205
x_block_ptr = tl.advance(x_block_ptr, (BLOCK_K, 0))
226206

@@ -308,7 +288,7 @@ def _nearest_kernel(
308288
),
309289
],
310290
key=['D', 'MAX_CAND'],
311-
# prune_configs_by={'early_config_prune': _prune_knn_configs},
291+
prune_configs_by={'early_config_prune': _prune_knn_configs},
312292
)
313293
@triton.heuristics({
314294
'NUM_D_BLOCKS': lambda args: triton.cdiv(args['D'], args['BLOCK_D']),
@@ -317,15 +297,15 @@ def _nearest_kernel(
317297
args['BLOCK_N'],
318298
),
319299
'EVEN_D': lambda args: args['D'] % args['BLOCK_D'] == 0,
300+
'K_PAD': lambda args: triton.next_power_of_2(args['K'])
320301
})
321302
@triton.jit
322303
def _knn_segmented_kernel(
323304
x_ptr,
324305
y_ptr,
325306
ptr_x_ptr,
326-
example_idx_ptr,
327-
row_ptr,
328-
col_ptr,
307+
batch_y_ptr,
308+
grid_ptr,
329309
M,
330310
N,
331311
D,
@@ -336,6 +316,8 @@ def _knn_segmented_kernel(
336316
stride_yd,
337317
K: tl.constexpr,
338318
K_PAD: tl.constexpr,
319+
USE_BATCH: tl.constexpr,
320+
INPUT_PRECISION: tl.constexpr,
339321
BLOCK_N: tl.constexpr,
340322
BLOCK_D: tl.constexpr,
341323
NUM_D_BLOCKS: tl.constexpr,
@@ -350,20 +332,22 @@ def _knn_segmented_kernel(
350332
mask_y = n_y < M # Mask for valid y.
351333

352334
k_offsets = tl.arange(0, K_PAD) # Offsets for padded top-k buffer.
353-
k_mask = k_offsets < K # Mask for real k.
354-
inf_bits = tl.full(
335+
INF_KEY = 0x7f800000ffffffff
336+
337+
best_dist_key = tl.full(
355338
(K_PAD,),
356-
float("inf"),
357-
tl.float32,
358-
).to(tl.int32, bitcast=True)
359-
best_dist_bits = inf_bits # Best distances (bitcast).
360-
best_idx = tl.full((K_PAD,), -1, tl.int32) # Best indices.
339+
INF_KEY,
340+
tl.int64,
341+
)
361342

362-
example_idx = tl.load(
363-
example_idx_ptr + n_y,
364-
mask=mask_y,
365-
other=0,
366-
) # Segment id.
343+
if USE_BATCH:
344+
example_idx = tl.load(
345+
batch_y_ptr + n_y,
346+
mask=mask_y,
347+
other=0,
348+
) # Segment id.
349+
else:
350+
example_idx = 0
367351
x_start = tl.load(
368352
ptr_x_ptr + example_idx,
369353
mask=mask_y,
@@ -376,7 +360,6 @@ def _knn_segmented_kernel(
376360
) # Segment end.
377361
if COSINE:
378362
y_sq = tl.zeros((1,), tl.float32) # ||y||^2 accumulator.
379-
y_sq_c = tl.zeros((1,), tl.float32)
380363
y_norm_ptr = tl.make_block_ptr(
381364
base=y_ptr,
382365
shape=(D, M),
@@ -394,23 +377,10 @@ def _knn_segmented_kernel(
394377
boundary_check=(0, 1),
395378
padding_option="zero",
396379
) # Tail D.
397-
y_term = tl.sum(y * y, axis=0)
398-
y_y = y_term - y_sq_c
399-
y_t = y_sq + y_y
400-
y_sq_c = (y_t - y_sq) - y_y
401-
y_sq = y_t
380+
y_sq += tl.sum(y * y, axis=0)
402381
y_norm_ptr = tl.advance(y_norm_ptr, (BLOCK_D, 0))
403382
y_rnorm = tl.rsqrt(y_sq + EPS) # 1/||y|| for cosine.
404383

405-
if stride_xm % 8 == 0:
406-
tl.multiple_of(stride_xm, 8) # Hint alignment.
407-
if stride_xd % 8 == 0:
408-
tl.multiple_of(stride_xd, 8)
409-
if stride_ym % 8 == 0:
410-
tl.multiple_of(stride_ym, 8)
411-
if stride_yd % 8 == 0:
412-
tl.multiple_of(stride_yd, 8)
413-
414384
for xb in range(MAX_X_BLOCKS):
415385
x_block_start = x_start + xb * BLOCK_N # Start of x block.
416386
offs_n = tl.arange(0, BLOCK_N) # Offsets within block.
@@ -430,12 +400,11 @@ def _knn_segmented_kernel(
430400
tl.multiple_of(offs_n, 8) # Hint vectorization.
431401
tl.max_contiguous(offs_n, 8)
432402

433-
acc_dot = tl.zeros((BLOCK_N,), tl.float32) # Dot accumulator.
434-
acc_dot_c = tl.zeros((BLOCK_N,), tl.float32)
435-
acc_x_sq = tl.zeros((BLOCK_N,), tl.float32) # ||x||^2 accumulator.
436-
acc_x_sq_c = tl.zeros((BLOCK_N,), tl.float32)
437-
acc_dist = tl.zeros((BLOCK_N,), tl.float32)
438-
acc_dist_c = tl.zeros((BLOCK_N,), tl.float32)
403+
if COSINE:
404+
acc_dot = tl.zeros((BLOCK_N,), tl.float32) # Dot accumulator.
405+
acc_x_sq = tl.zeros((BLOCK_N,), tl.float32) # ||x||^2 accumulator.
406+
else:
407+
acc_dist = tl.zeros((BLOCK_N,), tl.float32)
439408
x_block_start_i32 = x_block_start.to(
440409
tl.int32
441410
) # Block ptr needs int32 offsets.
@@ -483,28 +452,15 @@ def _knn_segmented_kernel(
483452
padding_option="zero",
484453
)
485454
if COSINE:
486-
prod = tl.dot(x, y, input_precision='ieee') # MxV dot.
487-
dot_term = tl.sum(prod, axis=1)
488-
dot_y = dot_term - acc_dot_c
489-
dot_t = acc_dot + dot_y
490-
acc_dot_c = (dot_t - acc_dot) - dot_y
491-
acc_dot = dot_t
492-
x_term = tl.sum(x * x, axis=1)
493-
x_y = x_term - acc_x_sq_c
494-
x_t = acc_x_sq + x_y
495-
acc_x_sq_c = (x_t - acc_x_sq) - x_y
496-
acc_x_sq = x_t
497-
x_block_ptr = tl.advance(x_block_ptr, (0, BLOCK_D))
455+
prod = tl.dot(x, y, input_precision=INPUT_PRECISION) # MxV dot.
456+
acc_dot += tl.sum(prod, axis=1)
457+
acc_x_sq += tl.sum(x * x, axis=1)
498458
y_block_ptr = tl.advance(y_block_ptr, (BLOCK_D, 0))
499459
else:
500460
diff = x - y
501-
term = tl.sum(diff * diff, axis=1)
502-
y_k = term - acc_dist_c
503-
t_k = acc_dist + y_k
504-
acc_dist_c = (t_k - acc_dist) - y_k
505-
acc_dist = t_k
506-
x_block_ptr = tl.advance(x_block_ptr, (0, BLOCK_D))
461+
acc_dist += tl.sum(diff * diff, axis=1)
507462
y_block_ptr = tl.advance(y_block_ptr, (0, BLOCK_D))
463+
x_block_ptr = tl.advance(x_block_ptr, (0, BLOCK_D))
508464
if COSINE:
509465
x_rnorm = tl.rsqrt(acc_x_sq + EPS) # 1/||x||.
510466
dist = 1.0 - acc_dot * (x_rnorm * y_rnorm) # Cosine distance.
@@ -520,53 +476,35 @@ def _knn_segmented_kernel(
520476
same_idx = same_idx & mask_x
521477
dist = tl.where(same_idx, float("inf"), dist)
522478

523-
524479
dist_bits = dist.to(tl.int32, bitcast=True)
525480
dist_bits = dist_bits.to(tl.int64) # Pack dist.
526481
idx_bits = x_idx.to(tl.int32).to(tl.int64) & 0xFFFFFFFF # Pack idx.
527482
key = (dist_bits << 32) | idx_bits # Lexicographic key.
528483
sorted_key = tl.sort(key, descending=False) # Full block sort.
529-
530-
k_valid = k_offsets < K # Only first k entries are valid.
531484
key_k = tl.gather(sorted_key, k_offsets, axis=0) # Take first k keys.
532-
dist_k_bits = (key_k >> 32).to(tl.int32) # Unpack dist bits.
533-
idx_k = (key_k & 0xFFFFFFFF).to(tl.int32) # Unpack idx.
534-
block_top_bits = tl.where(k_valid, dist_k_bits, inf_bits)
535-
block_top_idx = tl.where(k_valid, idx_k, -1) # Block top-k idx.
536-
537-
shift_idx = k_offsets - K # Shift window for block top-k.
538-
shift_valid = (k_offsets >= K) & (k_offsets < (2 * K))
539-
shift_idx_safe = tl.where(shift_valid, shift_idx, 0)
540-
shift_bits = tl.gather(block_top_bits, shift_idx_safe, axis=0)
541-
block_shifted_bits = tl.where(shift_valid, shift_bits, inf_bits)
542-
shift_idx_val = tl.gather(block_top_idx, shift_idx_safe, axis=0)
543-
block_shifted_idx = tl.where(shift_valid, shift_idx_val, -1)
544-
combo_bits = tl.where(
545-
k_offsets < K,
546-
best_dist_bits,
547-
block_shifted_bits,
485+
486+
combo_key = tl.cat(
487+
best_dist_key,
488+
key_k,
489+
can_reorder=True
548490
) # Merge buffers.
549-
combo_idx = tl.where(k_offsets < K, best_idx, block_shifted_idx)
550-
dist_bits = combo_bits.to(tl.int64) # Pack combo.
551-
idx_bits = combo_idx.to(tl.int32).to(tl.int64) & 0xFFFFFFFF
552-
combo_key = (dist_bits << 32) | idx_bits
553491
sorted_key = tl.sort(combo_key, descending=False) # Sort 2K keys.
554-
best_key = tl.gather(sorted_key, k_offsets, axis=0) # Keep best K.
555-
best_dist_bits = (best_key >> 32).to(tl.int32)
556-
best_idx = (best_key & 0xFFFFFFFF).to(tl.int32)
557-
558-
best_dist = best_dist_bits.to(tl.float32, bitcast=True)
559-
best_idx = tl.where(
560-
libdevice.isinf(best_dist),
561-
-1,
562-
best_idx,
563-
) # Mask invalid.
564-
565-
out_offsets = n_y * K + tl.where(k_mask, k_offsets, 0) # Output offsets.
492+
best_dist_key = tl.gather(sorted_key, k_offsets, axis=0) # Keep best K.
493+
494+
k_mask = k_offsets < K # Mask for real k.
495+
best_dist_key = tl.where(k_mask, best_dist_key, INF_KEY)
496+
best_idx = (best_dist_key & 0xFFFFFFFF)
497+
498+
out_offsets = n_y * K + k_offsets # Output offsets.
566499
out_mask = mask_y & k_mask # Valid output mask.
567-
tl.store(row_ptr + out_offsets, n_y, mask=out_mask) # Write row ids.
500+
501+
tl.store(
502+
grid_ptr + out_offsets,
503+
n_y,
504+
mask=out_mask,
505+
)
568506
tl.store(
569-
col_ptr + out_offsets,
507+
grid_ptr + M * K + out_offsets,
570508
best_idx.to(tl.int64),
571509
mask=out_mask,
572510
) # Write col idx.
@@ -689,7 +627,6 @@ def _radius_segmented_kernel(
689627
tl.max_contiguous(offs_n, 8)
690628

691629
acc_dist = tl.zeros((BLOCK_N,), tl.float32)
692-
acc_dist_c = tl.zeros((BLOCK_N,), tl.float32)
693630
x_block_start_i32 = x_block_start.to(tl.int32)
694631
x_block_ptr = tl.make_block_ptr(
695632
base=x_ptr,
@@ -725,16 +662,13 @@ def _radius_segmented_kernel(
725662
)
726663

727664
diff = x - y
728-
term = tl.sum(diff * diff, axis=1)
729-
y_k = term - acc_dist_c
730-
t_k = acc_dist + y_k
731-
acc_dist_c = (t_k - acc_dist) - y_k
732-
acc_dist = t_k
665+
acc_dist += tl.sum(diff * diff, axis=1)
733666
x_block_ptr = tl.advance(x_block_ptr, (0, BLOCK_D))
734667
y_row_ptr = tl.advance(y_row_ptr, (0, BLOCK_D))
735668

669+
dist = acc_dist
736670
active = count < max_neighbors
737-
mask = mask_x & (acc_dist < R2) & active
671+
mask = mask_x & (dist < R2) & active
738672
if IGNORE_SAME_INDEX:
739673
mask &= x_idx != n_y.to(tl.int64)
740674

torch_cluster/triton/knn.py

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def knn(
3232
Returns:
3333
Tensor: Edge index with shape [2, M * k].
3434
"""
35-
indices = segmented_topk_search(
35+
grid = segmented_topk_search(
3636
x,
3737
y,
3838
k,
@@ -41,9 +41,4 @@ def knn(
4141
cosine,
4242
batch_size,
4343
)
44-
row = torch.arange(y.size(0), device=y.device).repeat_interleave(k)
45-
col = indices.reshape(-1)
46-
valid = col >= 0
47-
row = row[valid]
48-
col = col[valid]
49-
return torch.stack([row, col], dim=0)
44+
return grid[:, grid[1] >= 0]

torch_cluster/triton/nearest.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ def grid(meta):
4949
USE_BATCH=False,
5050
COSINE=False,
5151
EPS=eps,
52+
INPUT_PRECISION="ieee",
5253
)
5354
return out
5455

@@ -93,5 +94,6 @@ def grid(meta):
9394
USE_BATCH=use_batch,
9495
COSINE=False,
9596
EPS=eps,
97+
INPUT_PRECISION="ieee",
9698
)
9799
return out

0 commit comments

Comments
 (0)