"""
GLM-4.5 Expert Selection Module for MoE Architecture
This module implements the expert selection logic for Mixture of Experts (MoE) architecture.
It intelligently assigns input tokens to different expert networks based on router logits,
supporting group-based top-k selection and weight renormalization.
Main Functions:
- select_experts: Main function for expert selection
- select_experts_kernel: JIT compiled kernel implementation
- process_main_loop_interation: Process a single batch iteration
"""
import os
import torch
import torch_npu
import numpy as np
from numpy.testing import assert_allclose
from torch._subclasses.fake_tensor import FakeTensor
from torch._dynamo import allow_in_graph
import pypto
from utils.get_format import get_format
def check_args(
router_logits: torch.Tensor,
top_k: int,
renormalize: bool,
topk_group: int,
num_expert_group: int,
e_score_correction_bias: torch.Tensor
) -> None:
assert router_logits.dim() == 2
assert router_logits.shape[1] == 160
assert get_format(router_logits) == 'ND'
assert router_logits.dtype == torch.float32
assert e_score_correction_bias.dim() == 1
assert e_score_correction_bias.shape[0] == 160
assert get_format(e_score_correction_bias) == 'ND'
assert e_score_correction_bias.dtype == torch.bfloat16
assert isinstance(top_k, int)
assert isinstance(renormalize, bool)
assert isinstance(topk_group, int)
assert isinstance(num_expert_group, int)
def process_main_loop_interation(
bs_idx,
logits_input,
e_score_bias_2d,
weight_k,
ids_k,
bs,
ne,
view_shape,
view_first,
topk,
topk_group,
num_expert_group,
renormalize_flag
):
"""
Process a single batch iteration for expert selection.
This function performs the following operations:
1. Apply sigmoid to router logits
2. Add expert score correction bias
3. Group experts and select top-k groups
4. Mask non-selected experts
5. Select top-k experts from masked logits
6. Optionally renormalize expert weights
Args:
bs_idx: Current batch index
logits_input: Router logits [num_tokens, num_router_experts]
e_score_bias_2d: Expert score correction bias [1, num_router_experts]
weight_k: Output tensor for top-k weights [num_tokens, topk]
ids_k: Output tensor for top-k expert IDs [num_tokens, topk]
bs: Batch size (number of tokens)
ne: Number of experts
view_shape: Shape for view operation
view_first: First dimension of view shape
topk: Number of top experts to select
topk_group: Number of expert groups for top-k selection
num_expert_group: Number of experts per group
renormalize_flag: Whether to renormalize expert weights
"""
tile_logits = pypto.view(logits_input, view_shape,
[bs_idx * view_shape[0], 0],
valid_shape=[(bs - bs_idx * view_shape[0]).min(view_shape[0]), ne])
pypto.set_vec_tile_shapes(view_first, ne)
tile_logits_fp32 = pypto.cast(tile_logits, pypto.DT_FP32)
e_score_bias_2d_cast = pypto.cast(e_score_bias_2d, tile_logits_fp32.dtype)
topk_weights = pypto.sigmoid(tile_logits_fp32)
topk_weights_add = pypto.add(topk_weights, e_score_bias_2d_cast)
group_unit = ne // num_expert_group
r1 = pypto.reshape(topk_weights_add,
[view_shape[0], num_expert_group, group_unit],
valid_shape=[(bs - bs_idx * view_shape[0]).min(view_shape[0]), num_expert_group,
group_unit])
pypto.set_vec_tile_shapes(view_first, num_expert_group, group_unit)
max1 = pypto.amax(r1, -1, False)
group_weight = max1
pypto.set_vec_tile_shapes(view_first, num_expert_group)
_, topk_group_indices = pypto.topk(group_weight, topk_group, -1, True)
topk_group_mask = pypto.full([view_shape[0], num_expert_group], 0.0, group_weight.dtype,
valid_shape=[(bs - bs_idx * view_shape[0]).min(view_shape[0]),
num_expert_group])
topk_group_mask_scatter_trans = pypto.scatter_(topk_group_mask, 1, topk_group_indices, 1.0)
twm_unsqueeze = pypto.unsqueeze(topk_group_mask_scatter_trans, -1)
pypto.set_vec_tile_shapes(view_first, num_expert_group, ne)
twm_expand = pypto.expand_clone(twm_unsqueeze, [view_shape[0], num_expert_group, group_unit],
valid_shape=[(bs - bs_idx * view_shape[0]).min(view_shape[0]),
num_expert_group, group_unit])
pypto.set_vec_tile_shapes(view_first, num_expert_group, group_unit)
twm_reshape = pypto.reshape(twm_expand,
[view_shape[0], ne],
valid_shape=[(bs - bs_idx * view_shape[0]).min(view_shape[0]), ne])
pypto.set_vec_tile_shapes(view_first, ne)
twm_not = pypto.logical_not(twm_reshape)
topk_weights_maskfill = pypto.where(twm_not, 0.0, topk_weights_add)
_, topk_ids = pypto.topk(topk_weights_maskfill, topk, -1, True)
tw_gather = pypto.gather(topk_weights, 1, topk_ids)
pypto.set_vec_tile_shapes(view_first, topk)
if pypto.symbolic_scalar(renormalize_flag):
denominator = pypto.sum(tw_gather, -1, True)
topk_weight_out = pypto.div(tw_gather, denominator)
else:
denominator = tw_gather
topk_weight_out = denominator
weight_k[bs_idx * view_shape[0]:, 0:] = topk_weight_out
ids_k[bs_idx * view_shape[0]:, 0:] = topk_ids
def select_experts_kernel(router_logits_shape, e_score_bias_shape, bs_top_k_frist, bs_top_k_second,
renormalize, topk_group, num_expert_group):
router_logits_shape = (pypto.frontend.dynamic("bs"), router_logits_shape[1])
topk_weights_shape = (pypto.frontend.dynamic("bs"), bs_top_k_frist[1])
topk_ids_shape = (pypto.frontend.dynamic("bs"), bs_top_k_second[1])
@pypto.frontend.jit(
runtime_options={"stitch_function_num_initial": 128,
"stitch_function_outcast_memory": 128,
"stitch_function_inner_memory": 128,
"stitch_cfgcache_size": 2500000}
)
def kernel(
logits: pypto.tensor(router_logits_shape, pypto.DT_FP32),
e_score_bias_input: pypto.tensor(e_score_bias_shape, pypto.DT_BF16),
weight_k: pypto.tensor(topk_weights_shape, pypto.DT_FP32),
index_k: pypto.tensor(topk_ids_shape, pypto.DT_INT32)
):
batch_size = logits.shape[0]
number_experts = logits.shape[1]
idx_k_shape = topk_ids_shape
topk = idx_k_shape[1]
view_shape = (1, number_experts)
view_first = 1
bs_loop = (batch_size + view_shape[0] - 1) // view_shape[0]
pypto.set_vec_tile_shapes(number_experts)
e_score_bias_2d = pypto.reshape(e_score_bias_input, [1, number_experts], inplace=True)
for bs_index in pypto.loop(bs_loop, name="LOOP_MOEGATE_L0", idx_name="bs_idx"):
process_main_loop_interation(
bs_index,
logits,
e_score_bias_2d,
weight_k,
index_k,
batch_size,
number_experts,
view_shape,
view_first,
topk,
topk_group,
num_expert_group,
renormalize
)
return kernel
def gen_row_idx_gloden(hidden_states, top_k):
num_tokens = hidden_states.shape[0]
row_idx_len = num_tokens * top_k
row_idx = (torch.arange(0, row_idx_len, dtype=torch.int32,
device=hidden_states.device).view(top_k, -1).permute(1, 0).contiguous())
return row_idx
def test_select_experts():
bs = 32
ne = 160
top_k = 8
topk_group = 1
num_expert_group = 1
renormalize = True
device_id = int(os.environ.get('TILE_FWK_DEVICE_ID', 0))
torch.npu.set_device(device_id)
for i in range(0, 2):
if i == 1:
bs = 1026
torch.manual_seed(0)
np.random.seed(0)
router_logits = torch.rand((bs, ne), dtype=torch.float32, device=f'npu:{device_id}')
e_score_bias = torch.rand((ne), dtype=torch.bfloat16, device=f'npu:{device_id}')
topk_weights = torch.rand((bs, top_k), dtype=torch.float32, device=f'npu:{device_id}')
topk_ids = torch.rand((bs, top_k), dtype=torch.int32, device=f'npu:{device_id}')
inputs = [router_logits, top_k, renormalize, topk_group, num_expert_group, e_score_bias, topk_weights, topk_ids]
g = torch.npu.NPUGraph()
with torch.npu.graph(g):
select_experts(*inputs)
g.replay()
router_logits_fp32 = router_logits.to(torch.float)
original_weights = router_logits_fp32.sigmoid()
bias_2d = e_score_bias.unsqueeze(0)
topk_weights_g_add = original_weights + bias_2d
tw_view = topk_weights_g_add.view(bs, num_expert_group, -1)
grouped_weights = tw_view.max(dim=-1).values
topk_group_indices_g = torch.topk(grouped_weights.to(torch.float32),
k=topk_group,
dim=-1,
sorted=False)[1]
topk_group_mask = torch.zeros_like(grouped_weights)
topk_group_mask.scatter_(1, topk_group_indices_g, 1)
tgm_unsquee = topk_group_mask.unsqueeze(-1)
tgm_expand = tgm_unsquee.expand(
bs, num_expert_group, ne // num_expert_group)
topk_weight_mask = tgm_expand.reshape(bs, -1)
logical_not_tmp = ~topk_weight_mask.bool()
topk_weights_fill = topk_weights_g_add.masked_fill(
logical_not_tmp, 0.0)
topk_ids_int64 = torch.topk(topk_weights_fill.to(torch.float32),
k=top_k,
dim=-1,
sorted=False)[1]
topk_ids_int32 = topk_ids_int64.to(torch.int32)
topk_weights_gather = original_weights.gather(1, topk_ids_int64)
if renormalize:
topk_weights_out = topk_weights_gather / \
topk_weights_gather.sum(dim=-1, keepdim=True)
else:
topk_weights_out = topk_weights_gather
topk_weight_2_tensor_list = topk_weights_out.cpu().flatten().tolist()
topk_ids_tensor_list = topk_ids_int32.cpu().flatten().tolist()
assert_allclose(np.array(topk_weights.cpu().flatten().tolist()),
np.array(topk_weight_2_tensor_list),
rtol=5e-3, atol=5e-3)
assert_allclose(np.array(topk_ids.cpu().flatten().tolist()),
np.array(topk_ids_tensor_list),
rtol=5e-3, atol=5e-3)
@allow_in_graph
def select_experts(
router_logits: torch.Tensor,
top_k: int,
renormalize: bool,
topk_group: int,
num_expert_group: int,
e_score_correction_bias: torch.Tensor,
topk_weights: torch.Tensor,
topk_ids: torch.Tensor
):
"""
Select top-k experts for each token based on router logits.
This function implements the expert selection mechanism for MoE architecture.
It uses a two-stage selection process:
1. First selects top-k expert groups
2. Then selects top-k experts from the selected groups
Args:
router_logits: Router logits [num_tokens, num_router_experts]
top_k: Number of top experts to select per token
renormalize: Whether to renormalize the routing weights
topk_group: Number of expert groups to select from
num_expert_group: Number of experts in each group
e_score_correction_bias: Correction bias to apply to expert scores [num_router_experts]
topk_weights: Output tensor for top-k expert weights [num_tokens, topk]
topk_ids: Output tensor for top-k expert IDs [num_tokens, topk]
Note:
This function is decorated with @allow_in_graph to enable integration
with PyTorch's compilation graph.
"""
if isinstance(router_logits, FakeTensor):
return
check_args(
router_logits,
top_k,
renormalize,
topk_group,
num_expert_group,
e_score_correction_bias
)
bs = router_logits.shape[0]
shapes = [router_logits.shape, e_score_correction_bias.shape, (bs, top_k), (bs, top_k), \
renormalize, topk_group, num_expert_group]
inputs = [router_logits, e_score_correction_bias, topk_weights, topk_ids]
select_experts_kernel(*shapes)(*inputs)
def main():
test_select_experts()
if __name__ == "__main__":
main()