Files
wehub-resource-sync b5ecf06f65
Code Format Check / format-check (push) Failing after 1s
chore: import upstream snapshot with attribution
2026-07-13 12:24:32 +08:00

181 lines
8.6 KiB
Python

import torch
def generate_topk_idx(rank_count: torch.Tensor, num_tokens: int, num_experts: int, num_ranks: int, num_topk: int) -> torch.Tensor:
"""
Map rank count to expert indices
"""
assert torch.equal(torch.sum(rank_count, dim=1), torch.ones(num_tokens, dtype=torch.int, device='cuda') * num_topk)
assert (num_tokens, num_ranks) == rank_count.shape
num_experts_per_rank = num_experts // num_ranks
# Generate base value
base_vals = torch.arange(num_experts, device='cuda').view(1, num_ranks, num_experts_per_rank).expand(num_tokens, num_ranks, num_experts_per_rank)
# Randomize the ordering within each row
rand_vals = torch.rand(num_tokens, num_ranks, num_experts_per_rank, device='cuda')
perm_indices = torch.argsort(rand_vals, dim=-1)
permuted = torch.gather(base_vals, 2, perm_indices)
# Create the mask
k_idx = torch.arange(num_experts_per_rank, device='cuda').view(1, 1, num_experts_per_rank).expand(num_tokens, num_ranks, num_experts_per_rank)
rank_count_expanded = rank_count.unsqueeze(2).expand(num_tokens, num_ranks, num_experts_per_rank)
mask = k_idx < rank_count_expanded
# Get the final indices by masking and reshaping
selected = permuted[mask] # (num_tokens * num_topk,)
topk_idx = selected.view(num_tokens, num_topk)
return topk_idx
def generate_rank_count(num_tokens: int, num_experts: int, num_ranks: int, num_topk: int, ratio: float) -> torch.Tensor:
"""
Generate rank count tensor for a given number of tokens, experts, ranks, and top-k.
This function generates a tensor of shape `(num_tokens, num_ranks)` where each element `[i, j]` represents
the number of topk experts that token `i` have on rank `j`. The distribution is such that
one special rank gets `ratio` times more traffic than the others.
"""
num_experts_per_rank = num_experts // num_ranks
num_normal_ranks = num_ranks - 1
assert ratio >= 1.0, 'ratio must be no less than 1.0'
# Generate rank count of each token from random distribution
random_scores = torch.rand(num_tokens, num_experts, device='cuda')
topk_weights_, topk_indices = torch.topk(random_scores, num_topk, dim=1, largest=True, sorted=False)
topk_indices //= num_experts_per_rank
sorted_topk_indices = torch.sort(topk_indices, dim=1)[0]
topk_indices_diff_mask = sorted_topk_indices[:, 1:] != sorted_topk_indices[:, :-1]
a = topk_indices_diff_mask.sum(dim=1) + 1
# Upper bound for this generating algorithm
upper_bound_per_token = int(num_normal_ranks / ratio) + 1
# Clamp the value in range [1, upper_bound_per_token] for each token
a = torch.clamp(a, None, upper_bound_per_token)
# Consider the special rank
sum_a = torch.sum(a).item()
normal_token_count = int(sum_a / (num_normal_ranks + ratio))
special_token_count = sum_a - normal_token_count * num_normal_ranks
special_token_count = min(special_token_count, int(normal_token_count * ratio) + 1)
# Tokens that the special rank must be in topk
must_mask = (a == num_ranks)
must_count = int(must_mask.sum().item())
special_token_count = max(must_count, special_token_count)
assert must_count <= special_token_count, 'Too many tokens with full rank assignment'
# Tokens that the special rank can optionally be in topk
optional_token_indices = torch.where(must_mask == 0)[0]
optional_token_indices = optional_token_indices[torch.randperm(num_tokens - must_count, device='cuda')][:special_token_count - must_count]
must_token_indices = torch.where(must_mask != 0)[0]
special_token_row_index = torch.cat(([must_token_indices, optional_token_indices]))
# Generate permutations for normal ranks
rank_perm = (torch.randperm(num_normal_ranks, device='cuda') + 1).repeat(num_tokens * num_topk // num_normal_ranks + 1)
# Compute cumulative sum of a to get starting indices in b for each row
a_cumsum = torch.cumsum(torch.cat((torch.tensor([0], device='cuda'), a)), dim=0)
row_starts = a_cumsum[:-1] # Starting indices for each row in b, shape (n,)
# Insert special rank index into the permutation for special tokens
rank_perm_with_special_rank = torch.zeros(num_tokens * num_topk, dtype=torch.long, device='cuda') # (n * k,)
special_token_mask = torch.zeros(num_tokens * num_topk, dtype=torch.bool, device='cuda')
special_token_flattened_row_index = row_starts[special_token_row_index]
special_token_mask[special_token_flattened_row_index] = 1
all_indices = torch.arange(num_tokens * num_topk, device='cuda')
non_special_indices = all_indices[special_token_mask != True]
rank_perm_with_special_rank[non_special_indices] = rank_perm[:len(non_special_indices)]
# Create column index grids
col_idx = torch.arange(num_topk, device='cuda').view(1, num_topk) # (1, num_topk)
# Compute modulo indices: col_idx % a[i] for each row
# torch.max is used to avoid zeros in case a[i] = 0 (which happens when the only topk rank is the special rank)
mod_idx = col_idx % a.view(num_tokens, 1) # (n, num_topk)
# Compute indices in b: row_start + (col % a[i])
b_indices = row_starts.view(num_tokens, 1) + mod_idx # (n, k)
# Gather values from b using computed indices
result = rank_perm_with_special_rank[b_indices]
# Shuffle rows randomly to avoid any pattern
shuffle_indices = torch.randperm(num_tokens, device='cuda')
result = result[shuffle_indices] # Shuffle rows
# Create rank count tensor
rank_count = torch.zeros((num_tokens, num_ranks), dtype=torch.int32, device='cuda')
rank_count.scatter_add_(dim=1, index=result, src=torch.ones_like(result, dtype=torch.int32))
return rank_count
def get_precise_unbalanced_scores(num_tokens: int, num_experts: int, num_ranks: int, num_topk: int, ratio: float):
"""
Generate precise unbalanced scores for testing.
Note that this function generates a distribution with precise unbalanced distribution,
which **differs from real distribution**.
"""
# Generate num topk experts for each rank
rank_count = generate_rank_count(num_tokens, num_experts, num_ranks, num_topk, ratio)
# Generate scores in a low distribution
threshold = 0.9
scores = torch.empty((num_tokens, num_experts), dtype=torch.float32, device='cuda')
scores.uniform_(to=threshold)
# Generate topk indices and change their scores to a high distribution
topk_idx = generate_topk_idx(rank_count, num_tokens, num_experts, num_ranks, num_topk)
topk_scores = torch.empty((num_tokens, num_topk), dtype=torch.float32, device='cuda')
topk_scores.uniform_(threshold + 1e-6, 1.0)
row_idx = torch.arange(num_tokens).unsqueeze(1).expand(num_tokens, num_topk)
scores[row_idx, topk_idx] = topk_scores
return scores
def get_scores_by_factor(num_tokens: int, num_experts: int, num_ranks: int, factor: float) -> torch.Tensor:
num_experts_per_rank = num_experts // num_ranks
scores = torch.empty((num_tokens, num_experts), dtype=torch.float32, device='cuda')
scores[:, :num_experts_per_rank].uniform_(to=factor)
scores[:, num_experts_per_rank:].uniform_(to=1)
return scores
def map_unbalanced_ratio_to_factor(num_tokens: int, num_experts: int, num_ranks: int, num_topk: int, ratio: float) -> float:
num_iterations = 20
factor_l, factor_r = 1.0, 100.0
num_experts_per_rank = num_experts // num_ranks
for _i in range(num_iterations):
factor_mid = (factor_l + factor_r) / 2
scores = get_scores_by_factor(num_tokens, num_experts, num_ranks, factor_mid)
_, topk_idx = torch.topk(scores, num_topk, dim=-1, largest=True, sorted=False)
rank_idx = topk_idx // num_experts_per_rank
one_hot = torch.nn.functional.one_hot(rank_idx, num_ranks)
counts = one_hot.any(dim=1).to(torch.float).sum(dim=0)
if counts[0].item() > counts[1:].mean().item() * ratio:
factor_r = factor_mid
else:
factor_l = factor_mid
return factor_l
def get_random_unbalanced_scores(num_tokens: int, num_experts: int, num_ranks: int, num_topk: int, ratio: float):
"""Generate unbalanced scores with a given ratio.
"""
factor = 1.0
if ratio != 1.0:
factor = map_unbalanced_ratio_to_factor(num_tokens, num_experts, num_ranks, num_topk, ratio)
return get_scores_by_factor(num_tokens, num_experts, num_ranks, factor)
def get_unbalanced_scores(num_tokens: int, num_experts: int, num_ranks: int, num_topk: int, ratio: float, precise: bool):
if precise:
return get_precise_unbalanced_scores(num_tokens, num_experts, num_ranks, num_topk, ratio)
else:
return get_random_unbalanced_scores(num_tokens, num_experts, num_ranks, num_topk, ratio)