Files
wehub-resource-sync 770d92cb1f
Lint / lint (push) Has been cancelled
Build Docs / Deploy Docs (push) Has been cancelled
Windows CI / Windows (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:23:58 +08:00

336 lines
15 KiB
Python

"""Operators for choosing the pivot to cut-off top-p percentile"""
import tvm
from tvm.script import tirx as T
from mlc_llm.support.max_thread_check import get_max_num_threads_per_block
# mypy: disable-error-code="attr-defined,valid-type,name-defined"
def top_p_pivot(pN, target: tvm.target.Target):
"""Top-p pivot function. This function finds the pivot to cut-off top-p percentile.
A valide pivot should satisfy the following conditions:
- lsum >= top_p
- top_p > lsum - cmin * lmin
where lsum is the sum of elements that are larger or equal to the pivot,
lmin is the minimum elements that is larger or equal to the pivot,
cmin is the count of elements that are equal to lmin,
Parameters
----------
prob:
The probability vector
top_p_arr:
The top-p threshold
init_pivots:
The initial pivot candidates
final_pivot:
The final pivot to cut-off top-p percentile
final_lsum:
The final sum of the values after top-p filtering.
"""
TX = 1024
K = 32
eps_LR = 1e-7
max_num_threads_per_block = get_max_num_threads_per_block(target)
TX = min(TX, max_num_threads_per_block)
def _var(dtype="int32"):
return T.sblock_alloc_buffer((1,), dtype, scope="local")
def valid(lsum, lmin, cmin, top_p):
return tvm.tirx.all(lsum >= top_p, top_p > lsum - cmin * lmin)
# fmt: off
@T.prim_func(private=True, s_tir=True)
def _func(
var_prob: T.handle,
var_top_p_arr: T.handle,
var_init_pivots: T.handle,
var_final_pivot: T.handle,
var_final_lsum: T.handle,
):
T.func_attr({"tirx.is_scheduled": 1, "tirx.noalias": True})
B = T.int32()
N = T.int32()
prob = T.match_buffer(var_prob, (B, N,), "float32")
top_p_arr = T.match_buffer(var_top_p_arr, (B,), dtype="float32")
init_pivots = T.match_buffer(var_init_pivots, (B, pN), "float32")
final_pivot = T.match_buffer(var_final_pivot, (B,), "float32")
final_lsum = T.match_buffer(var_final_lsum, (B,), "float32")
with T.sblock("kernel"):
pivot = T.sblock_alloc_buffer((pN,), "float32", scope="local")
top_p = _var("float32")
L = T.sblock_alloc_buffer((1,), "float32", scope="shared")
R = T.sblock_alloc_buffer((1,), "float32", scope="shared")
L_local = _var("float32")
R_local = _var("float32")
q = _var("float32")
lsum = T.sblock_alloc_buffer((pN,), "float32", scope="local")
lmin_broadcast = T.sblock_alloc_buffer((1), "float32", scope="shared")
lmin_broadcast_local = _var("float32")
lmin = T.sblock_alloc_buffer((pN,), "float32", scope="local")
cmin = T.sblock_alloc_buffer((pN,), "int32", scope="local")
total_sum = _var("float32")
it = _var("int32")
es_local = _var("bool")
es = T.sblock_alloc_buffer((1,), "bool", scope="shared")
find_pivot_local = _var("bool")
find_pivot = T.sblock_alloc_buffer((1,), "bool", scope="shared")
total_sum_reduce = _var("float32")
lsum_reduce = _var("float32")
lmin_reduce = _var("float32")
cmin_reduce = _var("int32")
for _bx in T.thread_binding(0, B, thread="blockIdx.x"):
for _tx in T.thread_binding(0, TX, thread="threadIdx.x"):
with T.sblock("CTA"):
b, tx = T.axis.remap("SS", [_bx, _tx])
top_p[0] = top_p_arr[b]
if tx == 0:
# leader thread initializes L, R
L[0] = 1.0 - top_p[0]
R[0] = eps_LR
find_pivot[0] = False
T.tvm_storage_sync("shared")
L_local[0] = L[0]
R_local[0] = R[0]
for i in T.unroll(0, pN):
# pivots are in descending order
pivot[i] = init_pivots[b, i]
find_pivot_local[0] = False
if L_local[0] - R_local[0] <= eps_LR:
# When the initial value is too small, set the result directly.
if tx == 0:
final_lsum[b] = 1.0
final_pivot[b] = 0.0
find_pivot_local[0] = True
while T.tvm_thread_invariant(
L_local[0] - R_local[0] > eps_LR
and T.Not(find_pivot_local[0])
):
# sync before each iteration
T.tvm_storage_sync("shared")
### get lsum, lmin, total_sum
for pidx in T.unroll(0, pN):
lsum[pidx] = 0.0
lmin[pidx] = T.max_value("float32")
cmin[pidx] = 0
total_sum[0] = 0.0
it[0] = 0
es_local[0] = False
while it[0] < T.ceildiv(N, TX) and T.Not(es_local[0]):
idx = T.meta_var(it[0] * TX + tx)
q[0] = T.if_then_else(idx < N, prob[b, idx], 0.0)
total_sum[0] += q[0]
for pidx in T.unroll(0, pN):
if q[0] >= pivot[pidx]:
lsum[pidx] += q[0]
if lmin[pidx] > q[0]:
lmin[pidx] = q[0]
cmin[pidx] = 1
elif lmin[pidx] == q[0]:
cmin[pidx] += 1
it[0] += 1
# early stop every K iterations
if it[0] % K == 0:
# reduce total_sum over tx
# T.tvm_storage_sync("shared")
with T.sblock("block_cross_thread"):
T.reads(total_sum[0])
T.writes(total_sum_reduce[0])
T.attr(
T.comm_reducer(lambda x0, y0: x0 + y0, [T.float32(0)]),
"reduce_scope",
T.int32(0),
)
T.tvm_thread_allreduce(T.uint32(1), total_sum[0], True, total_sum_reduce[0], tx, dtype="void") # noqa: E501
# T.tvm_storage_sync("shared")
if tx == 0:
# leader thread checks if we can stop early
es[0] = 1 - total_sum_reduce[0] < pivot[pN - 1]
T.tvm_storage_sync("shared")
es_local[0] = es[0]
T.tvm_storage_sync("shared")
# reduce lsum, lmin, cmin, over tx
for pidx in T.serial(0, pN):
# reduce lsum over tx for pivot[j]
with T.sblock("block_cross_thread"):
T.reads(lsum[pidx])
T.writes(lsum_reduce[0])
T.attr(
T.comm_reducer(lambda x0, y0: x0 + y0, [T.float32(0)]),
"reduce_scope",
T.int32(0),
)
T.tvm_thread_allreduce(T.uint32(1), lsum[pidx], True, lsum_reduce[0], tx, dtype="void") # noqa: E501
# reduce lmin over tx for pivot[j]
with T.sblock("block_cross_thread"):
T.reads(lmin[pidx])
T.writes(lmin_reduce[0])
T.attr(
T.comm_reducer(lambda x0, y0: T.min(x0, y0), [T.float32(0)]), # noqa: E501
"reduce_scope",
T.int32(0),
)
T.tvm_thread_allreduce(T.uint32(1), lmin[pidx], True, lmin_reduce[0], tx, dtype="void") # noqa: E501
if tx == 0:
# broadcast lmin to all threads
lmin_broadcast[0] = lmin_reduce[0]
T.tvm_storage_sync("shared")
lmin_broadcast_local[0] = lmin_broadcast[0]
if lmin[pidx] > lmin_broadcast_local[0]:
cmin[pidx] = 0
if tx == 0:
# only the leader thread updates lsum, lmin
lsum[pidx] = lsum_reduce[0]
lmin[pidx] = lmin_reduce[0]
# reduce cmin over tx for pivot[j]
with T.sblock("block_cross_thread"):
T.reads(cmin[pidx])
T.writes(cmin_reduce[0])
T.attr(
T.comm_reducer(lambda x0, y0: x0 + y0, [T.int32(0)]),
"reduce_scope",
T.int32(0),
)
T.tvm_thread_allreduce(T.uint32(1), cmin[pidx], True, cmin_reduce[0], tx, dtype="void") # noqa: E501
if tx == 0:
# only the leader thread updates cmin
cmin[pidx] = cmin_reduce[0]
T.tvm_storage_sync("shared")
if tx == 0:
# leader thread checks if we have found the pivot, or updates L, R
it[0] = 0
while it[0] < pN and T.Not(find_pivot_local[0]):
pidx = T.meta_var(it[0])
if valid(lsum[pidx], lmin[pidx], cmin[pidx], top_p[0]):
find_pivot[0] = True
find_pivot_local[0] = True
# write back the pivot and lsum
final_pivot[b] = pivot[pidx]
final_lsum[b] = lsum[pidx]
elif lsum[pidx] - lmin[pidx] * cmin[pidx] >= top_p[0]:
R[0] = pivot[pidx]
final_lsum[b] = lsum[pidx]
elif lsum[pidx] < top_p[0]:
L[0] = pivot[pidx]
it[0] += 1
T.tvm_storage_sync("shared")
L_local[0] = L[0]
R_local[0] = R[0]
find_pivot_local[0] = find_pivot[0]
# new pivots for next iteration
# uniform spacing between L and R
for pidx in T.unroll(0, pN):
pivot[pidx] = L[0] - (pidx + 1) * (L_local[0] - R_local[0]) / (pN + 1) # noqa: E501
if tx == 0:
# leader thread writes back the pivot
if T.Not(find_pivot_local[0]):
final_pivot[b] = R_local[0]
if R_local[0] == eps_LR:
final_lsum[b] = lsum[pN - 1]
# fmt: on
return _func
def top_p_renorm(target: tvm.target.Target = None):
"""Top-p renormalization function. This function renormalizes the probability vector.
Given the pivot, the probability vector is renormalized as follows:
- if prob >= pivot, renorm_prob = prob / lsum
- otherwise, renorm_prob = 0
Parameters
----------
prob:
The probability vector
final_pivot:
The final pivot to cut-off top-p percentile
final_lsum:
The sum of elements that are larger or equal to the pivot
renorm_prob:
The renormalized probability vector
"""
TX = 1024
CTA_COUNT = 512
if target:
max_num_threads_per_block = get_max_num_threads_per_block(target)
TX = min(TX, max_num_threads_per_block)
def _var(dtype="int32"):
return T.sblock_alloc_buffer((1,), dtype, scope="local")
# fmt: off
@T.prim_func(private=True, s_tir=True)
def _func(
var_prob: T.handle,
var_final_pivot: T.handle,
var_final_lsum: T.handle,
var_renorm_prob: T.handle,
):
T.func_attr({"tirx.is_scheduled": 1, "tirx.noalias": True})
B = T.int32()
N = T.int32()
prob = T.match_buffer(var_prob, (B, N,), "float32")
final_pivot = T.match_buffer(var_final_pivot, (B,), "float32")
final_lsum = T.match_buffer(var_final_lsum, (B,), "float32")
renorm_prob = T.match_buffer(var_renorm_prob, (B, N,), "float32")
with T.sblock("kernel"):
pivot = _var("float32")
lsum = _var("float32")
BX = T.meta_var(T.ceildiv(CTA_COUNT, B))
for _by in T.thread_binding(0, B, thread="blockIdx.y"):
for _bx in T.thread_binding(0, BX, thread="blockIdx.x"):
for _tx in T.thread_binding(0, TX, thread="threadIdx.x"):
with T.sblock("CTA"):
by, bx, tx = T.axis.remap("SSS", [_by, _bx, _tx])
pivot[0] = final_pivot[by]
lsum[0] = final_lsum[by]
for i in T.serial(T.ceildiv(N, BX * TX)):
idx = T.meta_var(i * BX * TX + bx * TX + tx)
if idx < N:
renorm_prob[by, idx] = T.if_then_else(prob[by, idx] >= pivot[0], prob[by, idx] / lsum[0], 0.0) # noqa: E501
# fmt: on
return _func