mega_moe
Applicable Products
Ascend 950PR/Ascend 950DT : SupportedAtlas A3 training products/Atlas A3 inference products : SupportedAtlas A2 training products/Atlas A2 inference products : SupportedAtlas 200I/500 A2 inference products : Not supportedAtlas inference products : Not supportedAtlas training products : Not supported
Function
Description:
The Mega MoE operator fuses the complete computation flow of the expert FFN in the MoE layer and the preceding and following data communication (that is, Dispatch + Linear1 + SwiGLU + Linear2 + Combine) into a single operator, achieving overlap of communication and computation. This operator provides APIs such as mega_moe and get_symm_buffer_for_mega_moe, which must be used together.
- get_symm_buffer_for_mega_moe: Must be used together with mega_moe. It encapsulates input parameters and creates a SymmBuffer structure, generating the information required for the mega_moe operator to run, such as
context,ep_world_size, andccl_buffer_size.
- get_symm_buffer_for_mega_moe: Must be used together with mega_moe. It encapsulates input parameters and creates a SymmBuffer structure, generating the information required for the mega_moe operator to run, such as
Formula:
Input:
- : activation matrix, corresponding input parameter
x. is the total number of global tokens, and is the hidden dimension. - : expert index matrix selected by tokens, corresponding input parameter
topk_ids. is the number of experts selected by each token. - : gating weight matrix of the experts selected by tokens, corresponding input parameter
topk_weights. - : weight matrix of Linear1, corresponding to input parameter
l1_weights. is the number of experts, and is the intermediate dimension. - : weight matrix of Linear2, corresponding input parameter
l2_weights.
- : activation matrix, corresponding input parameter
Output:
- : final output matrix, corresponding output parameter
y.
- : final output matrix, corresponding output parameter
Conventions:
- denotes matrix multiplication, and denotes element-wise multiplication.
- denotes rounding to the nearest integer, and denotes rounding down.
- takes the absolute value, and takes the maximum value.
- The set of all tokens is .
- The token representation (i.e., hidden state vector) of is , and .
- The expert index of is .
- The activation function , where is the Sigmoid function.
- . Here, the superscript of denotes the symmetric quantization range: its range is symmetrically rounded about 0, with endpoints and , which differs from the standard INT8 range of , hence the superscript is used to distinguish it.
- Tensor slicing uses Python-style
start:stop:stepnotation. For example, selects even rows, and selects odd rows. - denotes a binary reinterpretation operation that reinterprets the underlying binary data of tensor as the target type .
Computation Process
The data types of the activation matrix A and weight matrix W supported by each product during Linear computation are as follows:
Use Case A W A16W16 BFLOAT16 BFLOAT16 A8W8-INT INT8 INT8 A8W8-FP FLOAT8_E4M3FN, FLOAT8_E5M2 FLOAT8_E4M3FN, FLOAT8_E5M2 A8W4-INT INT8 INT4 Atlas A2 training products/Atlas A2 inference products ,Atlas A3 training products/Atlas A3 inference products : do not support the A8W8-FP use case in the preceding table.Ascend 950PR/Ascend 950DT : do not support the A16W16, A8W8-INT, and A8W4-INT use cases in the preceding table.
A16W16 non-quantized use case
EP Dispatch
In the Dispatch phase, each sends its token representation to experts . That is, for each , expert receives a copy of .
Let be the index set of all tokens dispatched to expert . The size of , , is the total number of tokens that expert must process. Then is the matrix formed by row-stacking in any fixed order the token representations of all satisfying . This matrix is all the token representations that expert receives after Dispatch.
For each and its selected -th expert , there is a unique row index such that . This mapping records the position of in the input matrix of expert .
Expert Compute
In an MoE layer, each expert is essentially an independent feedforward network (FFN) that uses the SwiGLU structure to improve expressive capacity. The entire computation process consists of the following three substeps.
1. Linear1 Projection
Linear1 projection is the first linear transformation of the expert network, producing the pre-activation values required by both the gate part and the up part. Its formula is
2. SwiGLU Activation
First split along the column dimension into the gate part and the up part . Then apply the SiLU activation function to the gate part and multiply it element-wise with the up part to obtain the intermediate activation representation of expert .
3. Linear2 Projection
Linear2 projection, as the second linear transformation, projects the intermediate activation representation from the high-dimensional space back to the original hidden dimension , making the expert output compatible with subsequent operations such as residual connections.
After the above computation, each output row of expert corresponds to a token in its batch. For , its output row in expert is .
Token Combine
Combine collects the output vectors computed by all experts, performs weighted summation according to the expert weight originally assigned to each token, and finally generates a fused output for each token. Using the previously recorded position index , it retrieves the rows belonging to from the output matrix of expert , multiplies them by the gating weight, and sums them up:
where is the gating weight of for expert .
The outputs of all tokens are stacked in input order as the final output .
A8W8-INT Quantization Use Case
Input
- : per-channel scaling factor of the Linear1 weight matrix, corresponding to the input parameter
l1_weights_sf. - : per-channel scaling factor of the Linear2 weight matrix, corresponding to the input parameter
l2_weights_sf.
- : per-channel scaling factor of the Linear1 weight matrix, corresponding to the input parameter
EP Dispatch
Before Dispatch communication, the original BF16 activation matrix is first quantized to INT8. For each , compute its per-token scaling factor:
Then quantize to obtain the INT8 representation:
During the Dispatch communication phase, each sends its quantized vector and scaling factor to experts ().
Let be the index set of all tokens dispatched to expert , and let be the total number of tokens that expert must process. Then is the matrix formed by row-stacking all satisfying in any fixed order. This matrix is the full set of token representations that expert receives after Dispatch. Similarly, the corresponding scaling factor vector received by expert is denoted as , whose elements are formed by stacking all satisfying in the same row order as .
For each and its selected -th expert , there exists a unique row index such that . This mapping records the position of in the input matrix of expert .
Expert Compute
In a MoE layer, each expert is essentially an independent feedforward network (FFN) that uses the SwiGLU structure to improve expressive capacity. In the A8W8 use case, both linear layers perform matrix multiplication with INT8 inputs and INT8 weights, producing INT32 intermediate results that are then dequantized. This is divided into three sub-steps.
1. Linear1 projection (INT8 matrix multiplication + dequantization)
The Linear1 projection is the first linear transformation of the expert network, producing the pre-activation values required by both the gate part and the up part. INT8 matrix multiplication is performed to obtain the INT32 result:
Then dequantize to the pre-activation value :
2. SwiGLU activation
First split along the column dimension into the gate part and the up part . Then apply the SiLU activation function to the gate part and multiply it element-wise with the up part to obtain the intermediate activation representation of expert .
3. Linear2 projection (quantization + INT8 matrix multiplication + dequantization)
The Linear2 projection is the second linear transformation. It projects the intermediate activation representation from the high-dimensional space back to the original hidden dimension , making the expert output compatible with subsequent operations such as residual connections. In the A8W8 use case, the activation values must be quantized to INT8. Therefore, first compute the scaling factor for each row (each token) of :
This yields the activation scaling factor of expert in the Linear2 computation. Then quantize:
Then perform INT8 matrix multiplication and dequantize:
After the above computation, each output row of expert corresponds to a token in its batch. For , its output row in expert is .
Token Combine
Combine collects the output vectors computed by all experts, performs weighted summation according to the expert weight originally assigned to each token, and finally generates a fused output for each token. Using the previously recorded position index , retrieve the rows belonging to from the output matrix of expert , multiply them by the gating weight, and sum them up:
where is the gating weight of for expert .
The outputs of all tokens are stacked in input order as the final output .
A8W4-INT Quantization Use Case
Input
- : Per-channel scaling factor of the Linear1 weight matrix, corresponding to the input parameter
l1_weights_sf. - : Per-channel scaling factor of the Linear2 weight matrix, corresponding to the input parameter
l2_weights_sf. - : Bias matrix of Linear1, generated offline during INT4 quantization, corresponding to the input parameter
l1_bias. - : Bias matrix of Linear2, generated offline during INT4 quantization, corresponding to the input parameter
l2_bias.
- : Per-channel scaling factor of the Linear1 weight matrix, corresponding to the input parameter
EP Dispatch
Before the Dispatch communication, the original BF16 activation matrix is first quantized to INT8. For each , its per-token scaling factor is computed as:
The INT8 representation is then obtained by quantization:
During the Dispatch communication phase, each sends its quantized vector and scaling factor to experts
Let be the index set of all tokens dispatched to expert , and let be the total number of tokens that expert must process. Then is the matrix formed by row-stacking all satisfying in any fixed order. This matrix is the full set of token representations that expert receives after Dispatch. Similarly, the corresponding scaling factor vector received by expert is denoted as , whose elements are all satisfying , stacked in the same row order as .
For each and its selected -th expert , there exists a unique row index such that . This mapping records the position of in the input matrix of expert .
Expert Compute
In an MoE layer, each expert is essentially an independent feedforward network (FFN) that uses the SwiGLU structure to improve expressive capacity. In the A8W4-INT use case, both linear layers use the MSD (Mixed-precision Split-activation Decomposition) scheme for matrix multiplication. By splitting an INT8 value into two signed INT4 values, the high 4 bits and the low 4 bits, an INT8×INT4 matrix multiplication can be decomposed into two INT4×INT4 matrix multiplications, thereby leveraging the hardware INT4 matrix multiplication acceleration. For the mathematical principle and implementation logic of this scheme, see GroupedMatmul W4A8 Quantization and MSD Scheme.
1. Generate a precision-compensated bias matrix (generated offline, completed outside the operator, and passed as an operator input)
The core of the MSD scheme is to split the quantized INT8 activation value into two INT4 components through binary reinterpretation. Because the low-bit INT4 component is defined as , its value range is mapped to , which is equivalent to subtracting 8 from the original unsigned low 4 bits (0 to 15). When the high-bit and low-bit INT4 values are separately multiplied by the weight matrix and then merged, this offset introduces a constant term that must be compensated in the final result.
Let the original INT8 activation matrix be , the split high-bit and low-bit INT4 activation matrices be and , and the weight matrix be . The recovery relationship is:
Here is an all-ones matrix with the same shape as , used to add 8 element-wise. Let be an all-ones column vector whose shape matches the input feature dimension. The matrix multiplication expands to:
Here denotes the sum over each column of the weight matrix , and the result is a row vector (with the same shape as the output dimension). In the implementation, this row vector is broadcast to the batch dimension and added to the output of all tokens. As shown, if only the first two terms are computed, a positive term is missing. To recover the result exactly, the compensation bias must be precomputed:
and are handled the same way. Each uses its corresponding all-ones column vector (with dimensions and , respectively) multiplied by its own weight matrix to obtain a bias row vector with matching shape. These biases are computed offline and then passed into the operator. They directly participate in the addition when merging the high-bit and low-bit results, thereby eliminating the error introduced by the offset.
2. Linear1 Projection (INT4 × INT4 Matrix Multiplication + Dequantization)
The Linear1 projection is the first linear transformation of the expert network, producing the pre-activation values required by both the gate part and the up part.
(1) Reinterpreting the Activation as INT4
Reinterpret the INT8 activation tensor as a binary view of two concatenated INT4 values:
The high-bit and low-bit components are directly given by the even rows and odd rows of :
They can be numerically obtained from the original INT8 as follows:
The recovery relation is . Since only changes the type view, and share the same underlying physical memory, requiring no data rearrangement or copy.
(2) INT4 × INT4 matrix multiplication and weight dequantization
Perform matrix multiplication between the reinterpreted INT4 activation view and the weight matrix , and apply the weight scaling factor for dequantization to obtain the result:
Record the results by even rows and odd rows as the following matrix views, which correspond to the computation results of and :
(3) Precision compensation and activation dequantization
Use the bias and the activation scaling factor (row vector) for precision compensation and activation dequantization respectively. The final pre-activation value is:
Here means multiplying each row of the matrix by the corresponding scalar in .
3. SwiGLU activation
First split along the column dimension into the gate part and the up part . Then apply the SiLU activation function to the gate part and multiply it element-wise with the up part to obtain the intermediate activation representation of expert .
4. Linear2 projection (quantization + INT4 × INT4 matrix multiplication + dequantization)
The Linear2 projection serves as the second linear layer, projecting the intermediate activation representation from the high-dimensional space back to the original hidden dimension , so that the expert output is compatible with subsequent operations such as residual connections. In the A8W4 use case, the activation values are first quantized to INT8, then binary-reinterpreted as a concatenated view of two INT4 values, completing the computation with a single matrix multiplication. The process is as follows.
(1) Activation quantization
Compute the scaling factor for each row of and quantize it to INT8:
Obtain the activation scaling factor for expert in the Linear2 computation, and compute the quantization result:
(2) Activation reinterpretation as INT4
Binary-reinterpret the INT8 activation tensor as a view of two alternately concatenated INT4 values:
The high and low components are directly given by the even rows and odd rows of :
Numerically, they can be obtained from the original INT8 as follows:
The recovery relationship is . Since only changes the type view, and share the underlying physical memory, without any data rearrangement or copy.
(3) INT4 × INT4 matrix multiplication and weight dequantization
Perform matrix multiplication on the reinterpreted INT4 activation view and the weight matrix , and apply the weight scaling factor for dequantization to obtain the result:
Record the result as the following matrix views by even rows and odd rows, which correspond to the computation results of and :
(4) Precision compensation and activation dequantization
Use the bias and the activation scaling factor (row vector) to perform precision compensation and activation dequantization respectively. The final output is:
Here means multiplying each row of the matrix by the corresponding scalar in .
After the preceding computation, each output row of expert corresponds to a token in its batch. For , its output row in expert is .
Token Combine
Combine collects the output vectors computed by all experts, performs a weighted sum according to the expert weight originally assigned to each token, and finally generates a fused output for each token. Using the previously recorded position index , it retrieves the rows belonging to from the output matrix of expert , multiplies them by the gating weight, and sums them up:
where is the gating weight of for expert .
The outputs of all tokens are stacked in input order to form the final output .
A8W8-FP Quantization Use Case
In the first phase, the input tokens are grouped and collected by expert, then quantized with MXFP8 to generate the quantized input and scaling factor of each expert:
Note: Tokens are sorted and gathered by expert according to topkIds. is the index set of tokens assigned to expert . indicates the maximum number of tokens received by the current expert, which may differ for each expert. is the corresponding submatrix. indicates MX per-group quantization (group size = 32). For each group of 32 elements, the shared exponent is extracted and then quantized to the FP8 target type (FLOAT8_E5M2 or FLOAT8_E4M3FN), while outputting the FLOAT8_E8M0 scaling factor. The quantized data serves as the input to GMM1.
In the second phase, GMM1 matrix multiplication (splitting into two halves along the column direction and computing them separately), SwiGLU activation, and MX quantization are performed for each expert:
Note: The first columns and the last columns of are respectively multiplied with the MX-dequantized input to obtain the Swish branch and the gating branch . The SwiGLU activation performs an element-wise product on the two branches, where is the Sigmoid function, reducing the intermediate dimension from to . The SwiGLU output is then MX-quantized to obtain the quantized input for GMM2.
In the third phase, GMM2 matrix multiplication is performed for each expert, and the results are distributed to the target ranks:
Note: The quantized SwiGLU output is multiplied with the second weight group after MX dequantization, mapping the -dimensional intermediate representation back to the -dimensional hidden space to obtain each expert's output . After the computation, the results are written to the remote expert offset address of the target rank through RDMA peermem, achieving cross-rank aggregation.
In the fourth stage, all tokens are weighted and summed by routing weights to restore the output to the same shape as the input:
Description: For each token , the corresponding row is fetched from the aggregated expert results according to the sorted routing index , and element-wise weighted accumulation is performed using the weights in topkWeights to obtain the final output .
Here, denotes the parameter x, denotes the parameter topkWeights, denotes the parameter weight1, denotes the parameter weight2, denotes the parameter y, and denotes the attribute moeExpertNum / epWorldSize (the number of experts per rank). denotes the second dimension of topkIds (the top-K value, which is 6 or 8).
Local variable description:
- : The index set of tokens routed to expert , determined after sorting
topkIds. - : The quantized input of expert and its MX scaling factor, an intermediate result of the first stage.
- and : The first columns and the last columns of the submatrix of corresponding to expert , derived by splitting
weight1according to SwiGLU. - and : The MX scaling factors corresponding to and , truncated from
weightScales1by dimension. - : The MX scaling factor corresponding to , from the parameter
weightScales2. - : Output of the two matrix multiplications in GMM1 (Swish branch and gating branch), an intermediate result.
- : SwiGLU activation output, with dimension , an intermediate result.
- : Quantized SwiGLU output and its MX scaling factor, an intermediate result.
- : Expert-level output of GMM2, with dimension , an intermediate result.
- : Row index of the -th top-k expert of token after expansion and sorting, determined by routing order.
- : MX per-group quantization operation, block size = 32, outputting FP8 data and E8M0 scaling factors.
- : MX per-group dequantization operation, implicitly performed inside matmul.
Prototype
- get_symm_buffer_for_mega_moe:
get_symm_buffer_for_mega_moe(group, num_experts, num_max_tokens_per_rank, num_topk, hidden, intermediate_hidden, *, max_recv_token_num=0, dispatch_quant_mode=0, dispatch_quant_out_dtype=None, combine_quant_mode=0, comm_alg="") -> SymmBuffer- mega_moe:
mega_moe(x, topk_ids, topk_weights, l1_weights, l2_weights, sym_buffer, *, l1_weights_sf=None, l2_weights_sf=None, l1_bias=None, l2_bias=None, x_active_mask=None, activation="swiglu", activation_clamp=None, weight1_type=None, weight2_type=None) -> (Tensor, Tensor)Parameters
get_symm_buffer_for_mega_moe
| Parameter | Type | Optional/Required | Description |
|---|---|---|---|
| group | str | Required | EP communicator name (expert parallel communicator). |
| num_experts | int | Required | Total number of experts in the MoE model. |
| num_max_tokens_per_rank | int | Required | Number of tokens on each device. When num_tokens differs across ranks, this is the maximum num_tokens. |
| num_topk | int | Required | Number of experts each token is sent to. |
| hidden | int | Required | Size of each token. |
| intermediate_hidden | int | Required | Projection dimension of the intermediate layer. |
| max_recv_token_num | int | Optional | Maximum number of tokens that each rank can receive. The default value 0 indicates automatic calculation. The default value is 0. |
| dispatch_quant_mode | int | Optional | Quantization mode for dispatch communication. 0 indicates non-quantization (A16W16 use case), 2 indicates INT8 quantization (A8W8-INT and A8W4-INT use cases), and 4 indicates MXFP quantization (A8W8-FP use case). For supported values of each product, see Constraints. The default value is 0. |
| dispatch_quant_out_dtype | torch.dtype | Optional | Data type of the output after dispatch quantization. Supports torch.int8, torch.float8_e5m2, and torch.float8_e4m3fn. For the supported values of each product, see Constraints. The default value is None. |
| combine_quant_mode | int | Optional | Quantization mode for combine communication. 0 indicates non-quantization, 3 indicates MXFP float8_e5m2, and 4 indicates MXFP float8_e4m3. For the supported values of each product, see Constraints. The default value is 0. |
| comm_alg | str | Optional | This parameter is not supported yet. Use the default value. The default value is "". |
mega_moe
The superscript 1 indicates that Atlas A2 training products/Atlas A2 inference products and Atlas A3 training products/Atlas A3 inference products do not support it. The superscript 2 indicates that Ascend 950PR/Ascend 950DT do not support it. For unsupported parameters, use the default value.
| Parameter | Parameter Type | Optional/Required | Description | Data Type | Data Format | Dimension (shape) |
|---|---|---|---|---|---|---|
| x | Tensor | Required | Token hidden states input to the MoE layer. | BFLOAT16 | ND | (num_tokens, hidden) |
| topk_ids | Tensor | Required | Expert index matrix, indicating the num_topk experts selected for each token. Element values range within [0, num_experts), and the num_topk experts selected for the same token cannot be duplicated. |
INT32 | ND | (num_tokens, num_topk) |
| topk_weights | Tensor | Mandatory | Gating weight coefficients corresponding to the num_topk experts selected by the expert gating network of the MoE model for the current input token. | BFLOAT162, FLOAT32 | ND | (num_tokens, num_topk) |
| l1_weights | list[Tensor] | Required | Weight matrix of the first linear layer of the expert network (including gate and up-projection), used to map the input to the intermediate dimension, with the output fed to the activation function. | BFLOAT162 | ND | (hidden, 2 × intermediate_hidden) |
| INT82 | FRACTAL_NZ | (hidden, 2 × intermediate_hidden) | ||||
| INT4(INT32)2 | FRACTAL_NZ | (hidden, 2 × intermediate_hidden // 8) | ||||
| FLOAT8_E5M21 | ND | (num_experts_per_rank, 2 × intermediate_hidden, hidden) | ||||
| FLOAT8_E4M3FN1 | ND | (num_experts_per_rank, 2 × intermediate_hidden, hidden) | ||||
| l2_weights | list[Tensor] | Required | Weight matrix of the second linear layer of the expert network, which projects the activated intermediate features back to the hidden dimension. The data type is the same as that of l1_weights. | BFLOAT162 | ND | (intermediate_hidden, hidden) |
| INT82 | FRACTAL_NZ | (intermediate_hidden, hidden) | ||||
| INT42 | FRACTAL_NZ | (intermediate_hidden, hidden) | ||||
| FLOAT8_E5M21 | ND | (num_experts_per_rank, hidden, intermediate_hidden) | ||||
| FLOAT8_E4M3FN1 | ND | (num_experts_per_rank, hidden, intermediate_hidden) | ||||
| sym_buffer | SymmBuffer | Mandatory | Structure created by the get_symm_buffer_for_mega_moe API. | SymmBuffer | SymmBuffer | SymmBuffer |
| l1_weights_sf | list[Tensor] | Optional | Quantization scaling factor of the weight matrix of the first linear layer of the expert network. | UINT642 | ND | (2 × intermediate_hidden, ) |
| FLOAT8_E8M01 | ND | (num_experts_per_rank, 2 × intermediate_hidden, CeilDiv(hidden, 64), 2) | ||||
| l2_weights_sf | list[Tensor] | Optional | Quantization scaling factor of the weight matrix of the second linear layer of the expert network. | UINT642 | ND | (hidden, ) |
| FLOAT8_E8M01 | ND | (num_experts_per_rank, hidden, CeilDiv(intermediate_hidden, 64), 2) | ||||
| l1_bias2 | list[Tensor] | Optional | Bias of the first linear layer of the expert network. Required only in the A8W4-INT quantization use case for precision compensation. | FLOAT32 | ND | (2 × intermediate_hidden, ) |
| l2_bias2 | list[Tensor] | Optional | Bias of the second linear layer of the expert network. Required only in the A8W4-INT quantization use case for precision compensation. | FLOAT32 | ND | (hidden, ) |
| x_active_mask2 | Tensor | Optional | Indicates whether the token participates in communication. | INT8 | ND | (num_tokens, ) |
| activation | str | Optional | Activation function type. Defaults to "swiglu". Currently only "swiglu" is supported. | str | Not applicable | Not applicable |
| activation_clamp | float | Optional | Symmetric clamp threshold for the activation function input. Limits the input tensor to the range [-activation_clamp, activation_clamp]. None means no clamping. The value must be ≥ 0. | float | Not applicable | Not applicable |
| weight1_type | Reserved parameter | Optional | Reserved parameter. Use the default value. | Reserved parameter | Not involved | Not involved |
| weight2_type | Reserved parameter | Optional | Reserved parameter. Use the default value. | Reserved parameter | Not applicable | Not applicable |
In the table, CeilDiv(x, y) = ⌈x / y⌉ = ⌊(x + y - 1) / y⌋ |
||||||
| In the table, T1(T2) indicates that data type T1 must be reinterpreted as data type T2 before being passed in. For example, INT4(INT32) indicates that the actual INT4 data must be reinterpreted as INT32 before being passed in, and its shape is the shape after reinterpretation. | ||||||
Return Value
get_symm_buffer_for_mega_moe
| Parameter | Type | Optional/Required | Description | Data Type | Dimension (shape) |
|---|---|---|---|---|---|
| sym_buffer | SymmBuffer | Required | Encapsulates the input parameters and generates `context`, `ep_world_size`, and `ccl_buffer_size`. | SymmBuffer | - |
mega_moe
| Parameter | Parameter Type | Optional/Required | Description | Data Type | Shape |
|---|---|---|---|---|---|
| y | Tensor | Required | Token data received by this device, corresponding to Y in the formula. The data type is the same as the input `x`. It must be a 2D tensor in ND format. Non-contiguous tensors are supported. | bfloat16 | (num_tokens, hidden) |
| expert_token_nums | Tensor | Required | Number of tokens actually received by each expert on this device. It must be a 1D tensor in ND format. Non-contiguous tensors are supported. | int32 | (num_experts_per_rank,) |
Constraints
The constraints on the list[Tensor] length, whether to transpose, and whether non-contiguous tensors are supported for each tensor parameter are as follows:
Parameter list[Tensor] Length Transposed Non-contiguous Tensor Supported x Not applicable No Yes topk_ids Not applicable No Supported topk_weights Not applicable No Supported l1_weights num_experts_per_rank (BFLOAT16/INT8/INT4 use case) or 1 (FLOAT8_E5M2/FLOAT8_E4M3FN use case) No (BFLOAT16/INT8/INT4 use case)/Yes (FLOAT8_E5M2/FLOAT8_E4M3FN use case) Not supported l2_weights num_experts_per_rank (BFLOAT16/INT8/INT4 use case) or 1 (FLOAT8_E5M2/FLOAT8_E4M3FN use case) No (BFLOAT16/INT8/INT4 use case)/Yes (FLOAT8_E5M2/FLOAT8_E4M3FN use case) Not supported l1_weights_sf num_experts_per_rank (UINT64 use case) or 1 (FLOAT8_E8M0 use case) No Not supported l2_weights_sf num_experts_per_rank (UINT64 use case) or 1 (FLOAT8_E8M0 use case) No Not supported l1_bias num_experts_per_rank No Not supported l2_bias num_experts_per_rank No Not supported x_active_mask Not involved No Supported Parameter consistency constraints:
- All input parameters of the mega_moe API and their corresponding tensor dimensions must be consistent with the same-named parameters of get_symm_buffer_for_mega_moe (for example,
num_experts,hidden,intermediate_hidden, etc.). - The values of parameters such as
num_experts,max_recv_token_num,dispatch_quant_mode,dispatch_quant_out_dtype, andnum_max_tokens_per_rankused during the API call must be consistent across all devices and across different layers in the network.
- All input parameters of the mega_moe API and their corresponding tensor dimensions must be consistent with the same-named parameters of get_symm_buffer_for_mega_moe (for example,
Communicator and networking constraints:
The values of
ep_world_sizeandccl_buffer_sizemust be consistent across all devices.The communicator buffer size must be consistent across all devices.
ccl_buffer_sizeis the total size (in bytes) of the CCL communication buffer allocated on HBM, containing two equal-sized spaces: windowIn and windowOut. Validation is based on a single space ofccl_buffer_size / 2, which must satisfy:Atlas A2 training products/Atlas A2 inference products:
offsetTokenPerExpert = ep_world_size × CeilAlign(ep_world_size × maxExpertPerRank + 1, 128) × 4B // winIn offsetAAfterDispatch = max_recv_token_num × (quant ? hidden + 512 : hidden × 2) offsetD = num_max_tokens_per_rank × num_topk × hidden × 2B winInTensorSize = offsetAAfterDispatch + offsetD // winOut offsetA = num_max_tokens_per_rank × num_topk × (quant ? hidden + 512 : hidden × 2) offsetC = max_recv_token_num × hidden × 2B winOutTensorSize = offsetA + offsetC offsetTensor = max(winInTensorSize, winOutTensorSize) + (quant ? max_recv_token_num × 4B : 0) // sync flags offsetFlag = ep_world_size × 512B + ep_world_size × maxExpertPerRank × 64B + ep_world_size × 64BAtlas A3 training products/Atlas A3 inference products:
offsetTokenPerExpert = ep_world_size × CeilAlign(ep_world_size × maxExpertPerRank + 1, 128) × 4B // winIn (A3 uses only winIn) offsetAAfterDispatch = num_max_tokens_per_rank × num_topk × (quant ? hidden + 512 : hidden × 2) offsetD = num_max_tokens_per_rank × num_topk × hidden × 2B winInTensorSize = offsetAAfterDispatch + offsetD offsetTensor = winInTensorSize + (quant ? num_max_tokens_per_rank × num_topk × 4B : 0) // sync flags syncStateReservedSize = 512KB offsetFlag = max(ep_world_size × 512B, syncStateReservedSize)Here,
ep_world_sizeis the communicator size, and indicates the maximum possible number of experts on each device. indicates whether dispatch quantization is enabled (dispatch_quant_mode = 2). The reserved 10 MB space is for internal metadata alignment and safety margin.The driver versions of all nodes in the communicator must be the same.
Atlas A2 training products/Atlas A2 inference products: Multi-device communicators require switch-based networking. Direct connection between two devices is not supported.
Atlas A3 training products/Atlas A3 inference products: Multi-device communicators must be within one SuperPoD. Direct connection between two devices and cross-SuperPoD networking are not supported.
Parameter constraints:
Common constraints:
num_experts: The value range is $[\text{ep_world_size},\ 1024], andnum_experts % ep_world_size == 0.
Atlas A2 training products/Atlas A2 inference products, Atlas A3 training products/Atlas A3 inference products:
num_tokensmust be consistent across devices, andnum_tokens(that is,x.dim0) on each device must be less than or equal tonum_max_tokens_per_rank.ep_world_size: only 2, 4, 8, 16, 32, and 64 are supported.num_experts_per_rank: value range $[1,\ 128], andnum_experts_per_rank = num_experts / ep_world_size.num_max_tokens_per_rank: value range $[1,\ 4096].max_recv_token_nummust be greater than 0. Input 0 indicates automatic calculation, with the formulanum_tokens × ep_world_size × min(num_topk, num_experts_per_rank).num_topk: value range $[1,\ 16].hidden: value range $[1024,\ 8192], andhidden % 512 == 0.intermediate_hidden: value range $[1024,\ 3072], andintermediate_hidden % 512 == 0dispatch_quant_mode: only 0 (non-quantized) and 2 (INT8 quantization) are supported.dispatch_quant_out_dtype: onlytorch.int8(corresponding to integer encoding 1) is supported.Use case matrix for A2/A3 products:
Use case x l1_weights l2_weights l1_weights_sf l2_weights_sf l1_bias l2_bias y dispatch_quant_mode dispatch_quant_out_dtype A16W16 BFLOAT16 BFLOAT16 BFLOAT16 – – – – BFLOAT16 0 – A8W8-INT BFLOAT16 INT8 INT8 UINT64 UINT64 – – BFLOAT16 2 torch.int8 A8W4-INT BFLOAT16 INT4(INT32) INT4(INT32) UINT64 UINT64 FLOAT32 FLOAT32 BFLOAT16 2 torch.int8
Ascend 950PR/Ascend 950DT:
num_tokens: value range $[1,\ 512].hidden: only 4096, 5120, and 7168 are supported.num_topk: only 6 and 8 are supported.num_experts_per_rank: value range $[1,\ 16], andnum_experts_per_rank = num_experts / ep_world_size.intermediate_hidden: Only 1024, 2048, 3072, 4096, and 7168 are supported.ep_world_size: Value range .num_max_tokens_per_rank: 0 or satisfiesnum_tokens == num_max_tokens_per_rank.dispatch_quant_out_dtype: Onlytorch.float8_e5m2(corresponding integer encoding 23) ortorch.float8_e4m3fn(corresponding integer encoding 24) is supported.dispatch_quant_mode: Only 4 (MXFP quantization mode) is supported. The dispatch phase uses MX per-group quantization (group size = 32), and the quantization scaling factor type is FLOAT8_E8M0.combine_quant_mode: Must be 0.comm_alg: Reserved parameter. Must be an empty string"".x_active_maskandscales: In the current version, a null pointer must be passed. Non-null input is not supported.- The data type of
yis the same as that ofx. - The dim1 (
intermediate_hidden) ofweight1must be twice the dim2 ofweight2. The shape ofweight1is(num_experts_per_rank, intermediate_hidden, hidden), and the shape ofweight2is(num_experts_per_rank, hidden, intermediate_hidden / 2). In the MXFP use case, whendispatch_quant_out_dtypeistorch.float8_e5m2,weight1andweight2must be FLOAT8_E5M2. When it istorch.float8_e4m3fn,weight1andweight2must be FLOAT8_E4M3FN. weight_scales1,weight_scales2: must not be null pointers. The shape ofweight_scales1is(num_experts_per_rank, intermediate_hidden, CeilDiv(hidden, 64), 2), and the shape ofweight_scales2is(num_experts_per_rank, hidden, CeilDiv(intermediate_hidden / 2, 64), 2). The dim3 of both must be 2. Where , .
Deterministic Computation
Defaults to a deterministic computation.
Example
Single-operator mode call:
The following example chains the two APIs in call order: first initialize the communicator, then construct sym_buffer with get_symm_buffer_for_mega_moe, and finally call mega_moe to run the operator.
Ascend 950PR/Ascend 950DT:
import os import torch import torch_npu from torch.multiprocessing import Process, Manager import torch.distributed as dist from torch.distributed import ReduceOp import torch.multiprocessing as mp from cann_ops_transformer.ops import get_symm_buffer_for_mega_moe, mega_moe import torchair E = 4 num_tokens = 256 H = 4096 N = 1024 topK = 6 num_experts = 8 server_num = 1 rank_per_dev = 2 world_size = server_num * rank_per_dev ep_ranks_list = [list(range(tp_id, world_size, 1)) for tp_id in range(1)] server_index = 0 def ceil(a, b): return (a + b - 1) // b def set_device(rank): torch_npu.npu.set_device(rank % rank_per_dev) print(f"current device set: {torch_npu.npu.current_device()}") def init_hccl_comm(rank): # Create the HCCL communication link and initialize the EP domain. print(f'[INFO] device_{rank} creates the HCCL communication link') master_ip = '127.0.0.1' dist.init_process_group(backend="hccl", rank=rank, world_size=world_size, init_method=f'tcp://{master_ip}:50001') print(f"device_{rank} init_process_group success") print(f"device {rank} initializes the EP domain") for ep_ranks in ep_ranks_list: tmp_group = dist.new_group(backend="hccl", ranks=ep_ranks) if rank in ep_ranks: ep_group = tmp_group ep_hcomm_info = ep_group._get_backend(torch.device("npu")).get_hccl_comm_name(rank) return ep_hcomm_info, ep_group def get_megamoe_kwargs( x, expert_ids, weights1, weights_scales1, weights2, weights_scales2, expert_scales ): x = x.to(torch.bfloat16).npu() expert_ids = expert_ids.to(torch.int32).npu() weights1 = weights1.to(torch.float8_e5m2).npu() weights_scales1 = weights_scales1.to(torch.float8_e8m0fnu).npu() weights2 = weights2.to(torch.float8_e5m2).npu() weights_scales2 = weights_scales2.to(torch.float8_e8m0fnu).npu() expert_scales = expert_scales.to(torch.bfloat16).npu() return { 'x': x, 'topk_ids': expert_ids, 'topk_weights': expert_scales, 'l1_weights': [weights1], 'l1_weights_sf': [weights_scales1], 'l2_weights': [weights2], 'l2_weights_sf': [weights_scales2], } def run_megamoe_npu( queue, rank, x, expert_ids, weights1, weights_scales1, weights2, weights_scales2, expert_scales ): print(f"{os.getpid()=}{rank=}") set_device(rank) print(f'[INFO] device_{rank} constructs the megamoe operator input data') megamoe_kwargs = get_megamoe_kwargs( x=x, expert_ids=expert_ids, weights1=weights1, weights_scales1=weights_scales1, weights2=weights2, weights_scales2=weights_scales2, expert_scales=expert_scales, ) # Step 1: Initialize the communicator. ep_hcomm_info, ep_group = init_hccl_comm(rank) # Step 2: Construct distribute_buffer (SymmBuffer struct). distribute_buffer = get_symm_buffer_for_mega_moe( ep_group, num_experts=num_experts, num_max_tokens_per_rank=num_tokens, num_topk=topK, hidden=H, intermediate_hidden=0, dispatch_quant_mode=4, dispatch_quant_out_dtype=torch.float8_e5m2 ) # Step 3: Run mega_moe and pass in the sym_buffer constructed in the previous step. y, expert_token_nums = mega_moe(**megamoe_kwargs, sym_buffer=distribute_buffer) torch.npu.synchronize() print(f"[INFO] device_{rank} finish\n") dist.destroy_process_group() print(f'rank {rank} epid {rank} npu finished! \n') queue.put([ rank, [ y.cpu(), expert_token_nums.cpu() ] ]) def gen_npu(target_func, **server_kwargs): def parse_rank_input(target_func, result_queue, rank, server_kwargs): ep_id = rank // 1 if target_func == run_megamoe_npu: return { "queue": result_queue, "rank": rank, "x": server_kwargs["x_list"][ep_id], "expert_ids": server_kwargs["expert_ids_list"][ep_id], "weights1": server_kwargs["weights1_list"][ep_id], "weights_scales1": server_kwargs["weights_scales1_list"][ep_id], "weights2": server_kwargs["weights2_list"][ep_id], "weights_scales2": server_kwargs["weights_scales2_list"][ep_id], "expert_scales": server_kwargs["expert_scales_list"][ep_id] } print("single_server scene!!!!!") rank_list = list(range(world_size)) print(f"rank list is: {rank_list}") proc_list = [] manager = Manager() result_queue = manager.Queue() mp.set_start_method("forkserver", force=True) for rank in rank_list: rank_kwargs = parse_rank_input(target_func, result_queue, rank, server_kwargs) proc = Process(target=target_func, kwargs=rank_kwargs) proc.start() proc_list.append(proc) rank_outputs = [None] * rank_per_dev for proc in proc_list: rank_id, rank_output = result_queue.get() local_rank_id = rank_id - server_index * rank_per_dev rank_outputs[local_rank_id] = rank_output for proc in proc_list: proc.join() if None in rank_outputs: print("[ERROR] Task failed! Please check the detailed error logs printed by the subprocesses.") exit(1) # Put the outputs of each category into the same list. category_outputs stores the list of outputs of each category. category_outputs = [] category_num = len(rank_outputs[0]) for category_id in range(category_num): specific_category_output = [rank_output[category_id] for rank_output in rank_outputs] category_outputs.append(specific_category_output) return category_outputs if __name__ == "__main__": x_shape = [num_tokens, H] expert_idx_shape = [num_tokens, topK] weight_shape = [E, N, H] weight_scale_shape = [E, N, ceil(H, 64), 2] output_shape = [num_tokens, N//2] weight2_shape = [E, H, N//2] weight2_scale_shape = [E, H, ceil(N//2, 64), 2] expert_scales_shape = [num_tokens, topK] # Construct the input. x = torch.randn(x_shape, dtype=torch.bfloat16) expert_scales = torch.randn(expert_scales_shape, dtype=torch.bfloat16) expert_ids = torch.stack( [torch.randperm(num_experts)[:topK] for _ in range(num_tokens)] ).to(torch.int32) weight1 = torch.randn(weight_shape, dtype=torch.float32).to(torch.float8_e5m2) weight_scales1 = torch.randint(125, 130, weight_scale_shape, dtype=torch.uint8).view(torch.float8_e8m0fnu) weight2 = torch.randn(weight2_shape, dtype=torch.float32).to(torch.float8_e5m2) weight_scales2 = torch.randint(125, 130, weight2_scale_shape, dtype=torch.uint8).view(torch.float8_e8m0fnu) golden_x_list = [x.clone() for _ in range(rank_per_dev)] golden_expert_ids_list = [expert_ids.clone() for _ in range(rank_per_dev)] golden_weights1_list = [weight1.clone() for _ in range(rank_per_dev)] golden_weights_scales1_list = [weight_scales1.clone() for _ in range(rank_per_dev)] golden_weights2_list = [weight2.clone() for _ in range(rank_per_dev)] golden_weights_scales2_list = [weight_scales2.clone() for _ in range(rank_per_dev)] golden_expert_scales_list = [expert_scales.clone() for _ in range(rank_per_dev)] [y, expert_token_nums] = gen_npu( run_megamoe_npu, x_list=golden_x_list, expert_ids_list=golden_expert_ids_list, weights1_list=golden_weights1_list, weights_scales1_list=golden_weights_scales1_list, weights2_list=golden_weights2_list, weights_scales2_list=golden_weights_scales2_list, expert_scales_list=golden_expert_scales_list, )Atlas A2 training products/Atlas A2 inference products, Atlas A3 training products/Atlas A3 inference products:
import torch import torch_npu import torch.multiprocessing as mp import torch.distributed as dist from cann_ops_transformer.ops import get_symm_buffer_for_mega_moe, mega_moe num_tokens = 64 hidden = 4096 intermediate_hidden = 1024 num_topk = 6 num_experts = 16 num_max_tokens_per_rank = 256 num_servers = 1 num_ranks_per_server = 2 server_index = 0 # The number of the current machine in the cluster. master_addr = '127.0.0.1' master_port = 50001 scene = 'A16W16' world_size = num_servers * num_ranks_per_server num_experts_per_rank = num_experts // world_size torch_npu.npu.config.allow_internal_format = True def init_hccl_comm(rank): print(f'device_{rank} start init_process_group') options = torch_npu._C._distributed_c10d.ProcessGroupHCCL.Options() # When using it, configure the value of hccl_buffer_size according to the constraint on the communicator buffer size given in the operator API document. options.hccl_config = {'hccl_buffer_size': 200} dist.init_process_group( backend='hccl', rank=server_index * num_ranks_per_server + rank, world_size=world_size, init_method=f'tcp://{master_addr}:{master_port}', pg_options=options, ) print(f'device_{rank} init_process_group success') ep_group = dist.new_group(backend='hccl', ranks=list(range(world_size))) _ = ep_group._get_backend(torch.device('npu')).get_hccl_comm_name(rank) return ep_group def bf16_mega_moe(rank): torch_npu.npu.set_device(rank % num_ranks_per_server) x = torch.randn((num_tokens, hidden), dtype=torch.bfloat16, device='npu') scores = torch.randn((num_tokens, num_experts), dtype=torch.float, device='npu') topk_weights, topk_ids = torch.topk(scores, num_topk, dim=-1, largest=True, sorted=False) topk_ids = topk_ids.to(torch.int32) l1_weights = [ torch.randn((hidden, intermediate_hidden * 2), dtype=torch.bfloat16, device='npu') for _ in range(num_experts_per_rank) ] l2_weights = [ torch.randn((intermediate_hidden, hidden), dtype=torch.bfloat16, device='npu') for _ in range(num_experts_per_rank) ] ep_group = init_hccl_comm(rank) sym_buffer = get_symm_buffer_for_mega_moe( ep_group, num_experts=num_experts, num_max_tokens_per_rank=num_max_tokens_per_rank, num_topk=num_topk, hidden=hidden, intermediate_hidden=intermediate_hidden, dispatch_quant_mode=0, dispatch_quant_out_dtype=None, ) y, expert_token_nums = mega_moe(x, topk_ids, topk_weights, l1_weights, l2_weights, sym_buffer) dist.barrier() dist.destroy_process_group() def int8_int8_mega_moe(rank): torch_npu.npu.set_device(rank % num_ranks_per_server) x = torch.randn((num_tokens, hidden), dtype=torch.bfloat16, device='npu') scores = torch.randn((num_tokens, num_experts), dtype=torch.float, device='npu') topk_weights, topk_ids = torch.topk(scores, num_topk, dim=-1, largest=True, sorted=False) topk_ids = topk_ids.to(torch.int32) l1_weights_bf16 = [ torch.randn((hidden, intermediate_hidden * 2), dtype=torch.bfloat16, device='npu') for _ in range(num_experts_per_rank) ] l2_weights_bf16 = [ torch.randn((intermediate_hidden, hidden), dtype=torch.bfloat16, device='npu') for _ in range(num_experts_per_rank) ] def per_channel_cast_to_int8(w: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]: max_abs = torch.amax(torch.abs(w), dim=0, keepdim=True) sf = torch.clamp(max_abs / 127.0, min=1e-8) q_weight = torch.round(w / sf).clamp(-127, 127).to(torch.int8) return q_weight, sf.squeeze().to(torch.float32) l1_weights_int8, l1_weights_sf_float = map(list, zip(*[per_channel_cast_to_int8(w) for w in l1_weights_bf16])) l2_weights_int8, l2_weights_sf_float = map(list, zip(*[per_channel_cast_to_int8(w) for w in l2_weights_bf16])) # Cast weights to NZ format for better performance. l1_weights = [torch_npu.npu_format_cast(w, torch_npu.Format.FRACTAL_NZ) for w in l1_weights_int8] l2_weights = [torch_npu.npu_format_cast(w, torch_npu.Format.FRACTAL_NZ) for w in l2_weights_int8] # Pack float32 scaling factors into uint64, which is required for the process of fixpipe dequantization. l1_weights_sf = [sf.view(torch.int32).to(torch.int64).view(torch.uint64) for sf in l1_weights_sf_float] l2_weights_sf = [sf.view(torch.int32).to(torch.int64).view(torch.uint64) for sf in l2_weights_sf_float] ep_group = init_hccl_comm(rank) sym_buffer = get_symm_buffer_for_mega_moe( ep_group, num_experts=num_experts, num_max_tokens_per_rank=num_max_tokens_per_rank, num_topk=num_topk, hidden=hidden, intermediate_hidden=intermediate_hidden, dispatch_quant_mode=2, dispatch_quant_out_dtype=torch.int8, ) y, expert_token_nums = mega_moe( x, topk_ids, topk_weights, l1_weights, l2_weights, sym_buffer, l1_weights_sf=l1_weights_sf, l2_weights_sf=l2_weights_sf, ) dist.barrier() dist.destroy_process_group() def int8_int4_mega_moe(rank): torch_npu.npu.set_device(rank % num_ranks_per_server) x = torch.randn((num_tokens, hidden), dtype=torch.bfloat16, device='npu') scores = torch.randn((num_tokens, num_experts), dtype=torch.float, device='npu') topk_weights, topk_ids = torch.topk(scores, num_topk, dim=-1, largest=True, sorted=False) topk_ids = topk_ids.to(torch.int32) l1_weights_bf16 = [ torch.randn((hidden, intermediate_hidden * 2), dtype=torch.bfloat16, device='npu') for _ in range(num_experts_per_rank) ] l2_weights_bf16 = [ torch.randn((intermediate_hidden, hidden), dtype=torch.bfloat16, device='npu') for _ in range(num_experts_per_rank) ] def per_channel_cast_to_int4(w: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]: max_abs = torch.amax(torch.abs(w), dim=0, keepdim=True) sf = torch.clamp(max_abs / 7.0, min=1e-8) q_weight = torch.round(w / sf).clamp(-7, 7).to(torch.int8) return q_weight, sf.squeeze().to(torch.float) def pack_int4_to_int8(x: torch.Tensor) -> torch.Tensor: x = (x + 8).to(torch.uint8).reshape(x.shape[0], -1, 2) x = ((x[..., 1] << 4) | x[..., 0]).view(torch.int8) return x l1_weights_int8, l1_weights_sf_float = map(list, zip(*[per_channel_cast_to_int4(w) for w in l1_weights_bf16])) l2_weights_int8, l2_weights_sf_float = map(list, zip(*[per_channel_cast_to_int4(w) for w in l2_weights_bf16])) l1_weights_int4 = [pack_int4_to_int8(w) for w in l1_weights_int8] l2_weights_int4 = [pack_int4_to_int8(w) for w in l2_weights_int8] # Cast weights to NZ format for better performance. l1_weights = [torch_npu.npu_format_cast(w, torch_npu.Format.FRACTAL_NZ).view(torch.int32) for w in l1_weights_int4] l2_weights = [torch_npu.npu_format_cast(w, torch_npu.Format.FRACTAL_NZ).view(torch.int32) for w in l2_weights_int4] # Pack float32 scaling factors into uint64, which is required for the process of fixpipe dequantization. l1_weights_sf = [sf.view(torch.int32).to(torch.int64).view(torch.uint64) for sf in l1_weights_sf_float] l2_weights_sf = [sf.view(torch.int32).to(torch.int64).view(torch.uint64) for sf in l2_weights_sf_float] l1_bias = [(w.float() * sf.unsqueeze(0)).sum(dim=0) * 8.0 for w, sf in zip(l1_weights_int8, l1_weights_sf_float)] l2_bias = [(w.float() * sf.unsqueeze(0)).sum(dim=0) * 8.0 for w, sf in zip(l2_weights_int8, l2_weights_sf_float)] ep_group = init_hccl_comm(rank) sym_buffer = get_symm_buffer_for_mega_moe( ep_group, num_experts=num_experts, num_max_tokens_per_rank=num_max_tokens_per_rank, num_topk=num_topk, hidden=hidden, intermediate_hidden=intermediate_hidden, dispatch_quant_mode=2, dispatch_quant_out_dtype=torch.int8, ) y, expert_token_nums = mega_moe( x, topk_ids, topk_weights, l1_weights, l2_weights, sym_buffer, l1_weights_sf=l1_weights_sf, l2_weights_sf=l2_weights_sf, l1_bias=l1_bias, l2_bias=l2_bias, ) dist.barrier() dist.destroy_process_group() if __name__ == '__main__': if scene == 'A16W16': torch.multiprocessing.spawn(bf16_mega_moe, nprocs=num_ranks_per_server) elif scene == 'A8W8-INT': torch.multiprocessing.spawn(int8_int8_mega_moe, nprocs=num_ranks_per_server) elif scene == 'A8W4-INT': torch.multiprocessing.spawn(int8_int4_mega_moe, nprocs=num_ranks_per_server) else: raise ValueError(f"Unsupported scene: {scene}, please choose from ['A16W16', 'A8W8-INT', 'A8W4-INT']")