GEMM¶
Batch GEMM on Blackwell SM100 tensor cores. |
|
Dense GEMM on Blackwell SM100 tensor cores. |
|
Fused SwiGLU activation + FP8/FP4 quantization in a single pass. |
|
Grouped contiguous GEMM for MoE on Blackwell. |
|
Grouped masked GEMM for MoE on Blackwell with per-expert token masking. |
|
Group-wise quantization from BF16/FP16 to FP8/FP4 with scale factors. |
|
Permute tokens into padded expert layout for masked GEMM. |
batch_gemm¶
- batch_gemm(
- input,
- weight,
- sfa,
- sfb,
- output,
- bias=None,
- alpha=None,
- sm_count=-1,
- mode='w8a8-mxfp8',
Batch GEMM on Blackwell SM100 tensor cores.
Computes
output[b] = input[b] @ weight[b]^Tfor each batch item. The batch dimensionBacts as the group (expert) index: batch itembis multiplied only byweight[b], i.e. there is a one-to-one mapping between input batch index and weight batch index (no separateexpert_idstensor is needed). Supports FP8 (W8A8-MXFP8) and both FP4 (W4A4) recipes (NVFP4 and MXFP4) via CUTLASS.- Parameters:
input (torch.Tensor) –
[B, M, K] CUDA tensor.
w8a8-mxfp8: dtype float8_e4m3fn.w4a4-*: dtype uint8 (packed FP4, logical K is size(2)*2).
weight (torch.Tensor) – [B, N, K] CUDA tensor. Same dtype/packing rules as input.
sfa (torch.Tensor) –
Scale factor for A, dtype uint8, CUTLASS SfAtom 128x4 swizzled layout. Shape:
[ceil(B * M / 128) * 128, sf_k], wheresf_k = K // 32(w8a8-mxfp8, w4a4-mxfp4) orK // 16(w4a4-nvfp4).w8a8-mxfp8,w4a4-mxfp4: UE8M0 encoding.w4a4-nvfp4: UE4M3 encoding.
sfb (torch.Tensor) – [B, N, sf_k] scale factor for B, dtype uint8, same SfAtom layout as sfa.
output (torch.Tensor) – [B, M, N] pre-allocated output tensor, dtype bfloat16. Written in-place.
bias (torch.Tensor | None) – Optional [B, N] bfloat16 bias vector. Only supported in w8a8-mxfp8 mode.
alpha (torch.Tensor | None) – Optional float32 per-batch scaling factor with shape [B], one value per batch item. Required for
w4a4-nvfp4; not accepted forw4a4-mxfp4.sm_count (int) – Number of SMs to use (-1 for all available).
mode (str) –
"w8a8-mxfp8","w4a4-mxfp4"or"w4a4-nvfp4".
- Returns:
None. The result is written in-place to output.
Example
from halops import batch_gemm, group_wise_quant b, m, n, k = 4, 256, 256, 256 x_bf16 = torch.randn(b, m, k, dtype=torch.bfloat16, device="cuda") w_bf16 = torch.randn(b, n, k, dtype=torch.bfloat16, device="cuda") x, sfa = group_wise_quant(x_bf16.view(b * m, k), mode="w8a8-mxfp8") w, sfb = group_wise_quant(w_bf16.view(b * n, k), mode="w8a8-mxfp8") x = x.view(b, m, k); sfa = sfa.view(b, m, -1) w = w.view(b, n, k); sfb = sfb.view(b, n, -1) out = torch.empty(b, m, n, dtype=torch.bfloat16, device="cuda") batch_gemm(x, w, sfa, sfb, out, mode="w8a8-mxfp8")
dense_gemm¶
- dense_gemm(
- input,
- weight,
- sfa,
- sfb,
- output,
- bias=None,
- alpha=None,
- sm_count=-1,
- mode='w8a8-mxfp8',
- use_pdl=True,
Dense GEMM on Blackwell SM100 tensor cores.
Computes
output = input @ weight^T + bias. Supports FP8 (W8A8-MXFP8) and FP4 (W4A4) quantized GEMM via CUTLASS, where the FP4 recipe is selected explicitly bymode.- Parameters:
input (torch.Tensor) –
[M, K] CUDA tensor.
w8a8-mxfp8: dtype float8_e4m3fn.w4a4-*: dtype uint8 (packed FP4, physical shape [M, K // 2]).
weight (torch.Tensor) – [N, K] CUDA tensor. Same dtype and packing rules as input.
sfa (torch.Tensor) –
Scale factor for A, dtype uint8, CUTLASS SfAtom 128x4 swizzled layout. The layout covers whole 128-row atoms, so the shape is
[ceil(M / 128) * 128, K // 32](w8a8-mxfp8, w4a4-mxfp4) or[ceil(M / 128) * 128, K // 16](w4a4-nvfp4) – exactly whatgroup_wise_quantreturns.w8a8-mxfp8,w4a4-mxfp4: UE8M0 encoding.w4a4-nvfp4: UE4M3 encoding.
sfb (torch.Tensor) – Scale factor for B, dtype uint8, in the same ordinary SfAtom layout as sfa. Shape [N, K // 32] (w8a8-mxfp8, w4a4-mxfp4) or [N, K // 16] (w4a4-nvfp4).
output (torch.Tensor) – [M, N] pre-allocated output tensor, dtype bfloat16. Written in-place.
bias (torch.Tensor | None) – Optional [N] bfloat16 bias vector. Only supported in w8a8-mxfp8 mode.
alpha (torch.Tensor | None) – Optional float32 scaling factor with shape [1]. Required for
w4a4-nvfp4; not accepted forw4a4-mxfp4.sm_count (int) – Number of SMs to use (-1 for all available).
mode (str) – Quantization mode:
"w8a8-mxfp8","w4a4-mxfp4"or"w4a4-nvfp4".use_pdl (bool) – Enable Programmatic Dependent Launch (on by default).
- Returns:
None. The result is written in-place to output.
Example
from halops import dense_gemm, group_wise_quant m = n = k = 256 x_bf16 = torch.randn(m, k, dtype=torch.bfloat16, device="cuda") w_bf16 = torch.randn(n, k, dtype=torch.bfloat16, device="cuda") x, sfa = group_wise_quant(x_bf16, mode="w8a8-mxfp8") w, sfb = group_wise_quant(w_bf16, mode="w8a8-mxfp8") out = torch.empty(m, n, dtype=torch.bfloat16, device="cuda") dense_gemm(x, w, sfa, sfb, out, mode="w8a8-mxfp8")
fused_swiglu_quant¶
- fused_swiglu_quant(
- input,
- token_nums_per_expert,
- is_fp4=False,
Fused SwiGLU activation + FP8/FP4 quantization in a single pass.
Computes SwiGLU:
y = up * silu(gate)where input = [gate | up] (concatenated along dim-1), then quantizes the result to FP8 or FP4. Outputs quantized data and CUTLASS SfAtom-formatted scale factors.- Parameters:
input (torch.Tensor) – [E * M, 2 * hidden_size] bfloat16. First half of dim-1 is gate, second half is up projection.
token_nums_per_expert (torch.Tensor) – [E] int32. Valid token count per expert.
is_fp4 (bool) –
Truefor FP4 E2M1 output (group_size=16);Falsefor FP8 E4M3 output (group_size=32).
- Returns:
A tuple
(out_quant, out_scale)where –out_quant:FP4: [E*M, hidden_size/2] uint8 (packed nibbles)
FP8: [E*M, hidden_size] float8_e4m3fn
out_scale: [E*M, kb_dim] uint8, CUTLASS SfAtom 128x4 interleaved layout.FP8:
kb_dim = ceil(hidden_size / 128 / 4) * 16FP4:
kb_dim = hidden_size / 16
- Return type:
Tuple[torch.Tensor, torch.Tensor]
Example
from halops import fused_swiglu_quant e, m, hidden_size = 4, 128, 2048 gate_up = torch.randn(e * m, 2 * hidden_size, dtype=torch.bfloat16, device="cuda") token_nums_per_expert = torch.full((e,), m, dtype=torch.int32, device="cuda") out_quant, out_scale = fused_swiglu_quant( gate_up, token_nums_per_expert, is_fp4=False)
group_contiguous_gemm¶
- group_contiguous_gemm(
- input,
- weight,
- sfa,
- sfb,
- expert_ids,
- output,
- bias=None,
- alpha=None,
- sm_count=-1,
- mode='w8a8-mxfp8',
Grouped contiguous GEMM for MoE on Blackwell.
Unlike masked GEMM, tokens from different experts are packed contiguously (no per-expert padding).
expert_idsmaps each token row to its corresponding expert weight.- Parameters:
input (torch.Tensor) –
[M, K] CUDA tensor. All tokens packed contiguously.
w8a8-mxfp8: dtype float8_e4m3fn.w4a4-*: dtype uint8 (packed FP4, logical K is size(1)*2).
weight (torch.Tensor) – [num_experts, N, K] 3D CUDA tensor. Per-expert weights.
sfa (torch.Tensor) –
Scale factor for A, dtype uint8, CUTLASS SfAtom 128x4 swizzled layout. Shape:
[ceil(M / 128) * 128, sf_k], wheresf_k = K // 32(w8a8-mxfp8, w4a4-mxfp4) orK // 16(w4a4-nvfp4).w8a8-mxfp8,w4a4-mxfp4: UE8M0 encoding.w4a4-nvfp4: UE4M3 encoding.
sfb (torch.Tensor) – Scale factor for B, dtype uint8, same SfAtom layout as sfa. shape: [num_experts, N, K // 32] (w8a8-mxfp8, w4a4-mxfp4) or [num_experts, N, K // 16] (w4a4-nvfp4).
expert_ids (torch.Tensor) – [M] int32. Maps each row to an expert index in [0, num_experts). Must be sorted in non-decreasing order.
output (torch.Tensor) – [M, N] bfloat16 output. Written in-place.
bias (torch.Tensor | None) – Optional [num_experts, N] bfloat16 bias. Only supported in w8a8-mxfp8 mode.
alpha (torch.Tensor | None) – Optional float32 per-expert scale with shape [num_experts]. Required for
w4a4-nvfp4; not accepted forw4a4-mxfp4.sm_count (int) – Number of SMs to use (-1 for all available).
mode (str) –
"w8a8-mxfp8","w4a4-mxfp4"or"w4a4-nvfp4".
- Returns:
None. The result is written in-place to output.
Example
from halops import group_contiguous_gemm, group_wise_quant m, n, k = 256, 256, 256 num_experts = 4 x_bf16 = torch.randn(m, k, dtype=torch.bfloat16, device="cuda") w_bf16 = torch.randn(num_experts, n, k, dtype=torch.bfloat16, device="cuda") x, sfa = group_wise_quant(x_bf16, mode="w8a8-mxfp8") w, sfb = group_wise_quant(w_bf16.view(num_experts * n, k), mode="w8a8-mxfp8") w = w.view(num_experts, n, k) sfb = sfb.view(num_experts, n, -1) # first 128 rows -> expert 0, next 128 -> expert 1 expert_ids = torch.arange(m, device="cuda", dtype=torch.int32) // 128 out = torch.empty(m, n, dtype=torch.bfloat16, device="cuda") group_contiguous_gemm(x, w, sfa, sfb, expert_ids, out, mode="w8a8-mxfp8")
group_masked_gemm¶
- group_masked_gemm(
- input,
- weight,
- sfa,
- sfb,
- masked_m,
- output,
- bias=None,
- alpha=None,
- max_m_per_expert=0,
- sm_count=-1,
- mode='w8a8-mxfp8',
Grouped masked GEMM for MoE on Blackwell with per-expert token masking.
Tokens are pre-arranged in a padded
[num_experts * max_m_per_expert, K]layout.masked_mspecifies how many rows are valid per expert, enabling early-exit for padded rows.- Parameters:
input (torch.Tensor) –
[num_experts * max_m_per_expert, K] CUDA tensor.
w8a8-mxfp8: dtype float8_e4m3fn.w4a4-*: dtype uint8 (packed FP4, logical K is size(1)*2).
weight (torch.Tensor) – [num_experts, N, K] 3D CUDA tensor. Per-expert weights.
sfa (torch.Tensor) –
Scale factor for A, dtype uint8, CUTLASS SfAtom 128x4 swizzled layout. Shape:
[num_experts * max_m_per_expert, sf_k], wheresf_k = K // 32(w8a8-mxfp8, w4a4-mxfp4) orK // 16(w4a4-nvfp4).w8a8-mxfp8,w4a4-mxfp4: UE8M0 encoding.w4a4-nvfp4: UE4M3 encoding.
sfb (torch.Tensor) – Scale factor for B, dtype uint8, same SfAtom layout as sfa, shape [num_experts, N, sf_k].
masked_m (torch.Tensor) – [num_experts] int32. Actual valid token count per expert.
output (torch.Tensor) – [num_experts * max_m_per_expert, N] bfloat16 output. Written in-place.
bias (torch.Tensor | None) – Optional [num_experts, N] bfloat16 bias. Only supported in w8a8-mxfp8 mode.
alpha (torch.Tensor | None) – Optional float32 per-expert scale with shape [num_experts]. Required for
w4a4-nvfp4; not accepted forw4a4-mxfp4.max_m_per_expert (int) – Maximum tokens per expert (padding bound), must be > 0.
sm_count (int) – Number of SMs to use (-1 for all available).
mode (str) –
"w8a8-mxfp8","w4a4-mxfp4"or"w4a4-nvfp4".
- Returns:
None. The result is written in-place to output.
Example
from halops import group_masked_gemm, group_wise_quant num_experts, max_m_per_expert, n, k = 4, 128, 256, 256 m = num_experts * max_m_per_expert x_bf16 = torch.randn(m, k, dtype=torch.bfloat16, device="cuda") w_bf16 = torch.randn(num_experts, n, k, dtype=torch.bfloat16, device="cuda") x, sfa = group_wise_quant(x_bf16, mode="w8a8-mxfp8") w, sfb = group_wise_quant(w_bf16.view(num_experts * n, k), mode="w8a8-mxfp8") w = w.view(num_experts, n, k) sfb = sfb.view(num_experts, n, -1) # valid rows of each expert's 128-row slot masked_m = torch.tensor([128, 64, 0, 32], dtype=torch.int32, device="cuda") out = torch.empty(m, n, dtype=torch.bfloat16, device="cuda") group_masked_gemm( x, w, sfa, sfb, masked_m, out, max_m_per_expert=max_m_per_expert, mode="w8a8-mxfp8", )
group_wise_quant¶
- group_wise_quant(
- input,
- mode='w8a8-mxfp8',
- use_swizzle=True,
- use_pdl=True,
Group-wise quantization from BF16/FP16 to FP8/FP4 with scale factors.
Quantizes a 2D BF16/FP16 tensor per group along K, producing both the quantized data and the block-scale tensor. Supports FP8 (W8A8-MXFP8) and FP4 (W4A4) quantization. The FP4 recipe is selected explicitly by
mode.The output pairs feed
dense_gemm/batch_gemm/group_masked_gemm/group_contiguous_gemmof the samemode.- Parameters:
input (torch.Tensor) – [m, k] CUDA tensor, dtype bfloat16 or float16. Source tensor.
mode (str) –
"w8a8-mxfp8","w4a4-mxfp4"or"w4a4-nvfp4". Selects both the value encoding and the scale recipe.use_swizzle (bool) –
Trueto output scales in the CUTLASS SfAtom 128x4 interleaved layout consumed by the GEMM kernels;Falsefor a plain row-major scale layout.use_pdl (bool) – Enable Programmatic Dependent Launch (on by default).
- Returns:
A tuple
(output_quant, output_sf)where –output_quant:FP8: [m, k] float8_e4m3fn
FP4: [m, k/2] uint8 (packed nibbles, even index = low nibble)
output_sf: [ceil(m/128)*128, k / group_size] uint8w8a8-mxfp8,w4a4-mxfp4: UE8M0 encoding (power-of-2 biased exponent)w4a4-nvfp4: UE4M3 encoding (fp8_e4m3fn bit pattern as unsigned byte)
- Return type:
Tuple[torch.Tensor, torch.Tensor]
Example
from halops import group_wise_quant m, k = 256, 512 x_bf16 = torch.randn(m, k, dtype=torch.bfloat16, device="cuda") # FP8 (W8A8-MXFP8) quantization x_fp8, sf = group_wise_quant(x_bf16, mode="w8a8-mxfp8") # FP4 with UE8M0 scales, one per 32 elements (MXFP4) x_mxfp4, sf = group_wise_quant(x_bf16, mode="w4a4-mxfp4") # FP4 with UE4M3 scales, one per 16 elements (NVFP4) x_nvfp4, sf = group_wise_quant(x_bf16, mode="w4a4-nvfp4")
prefill_permute_to_masked_gemm¶
- prefill_permute_to_masked_gemm(
- x,
- scale,
- topk_ids,
- num_local_experts,
- max_token_num,
Permute tokens into padded expert layout for masked GEMM.
Rearranges tokens from a sorted/topk-selected layout into
[E, max_token_num, hidden]padded format suitable for group_masked_gemm, and simultaneously converts scale factors from linear row-major into CUTLASS SfAtom layout.- Parameters:
x (torch.Tensor) – [num_tokens, hidden] CUDA tensor. dtype float8_e4m3fn or bfloat16.
scale (torch.Tensor) – [num_tokens, packed_hidden_scale] CUDA tensor. dtype int32 (broadcast mode): packed_hidden_scale = hidden/128/4, each int32 holds 4 per-group-128 scale bytes. dtype uint8 (direct mode): packed_hidden_scale = hidden/128*4, each 4 consecutive bytes form one uint32 store.
topk_ids (torch.Tensor) – [num_tokens, topk] int64. Expert assignment per token; -1 means unassigned/padding.
num_local_experts (int) – Number of local experts (E).
max_token_num (int) – Maximum tokens per expert. Must be >= 128 and divisible by 128. Must also be large enough to hold the tokens routed to a single expert; the kernel does not clamp, so an expert receiving more than
max_token_numtokens writes out of bounds (illegal memory access). Size it with headroom abovenum_tokens * topk / num_local_experts.
- Returns:
A tuple
(permute_x, output_scale, permuted_indice_map, token_nums_per_expert)–permute_x: [E * max_token_num, hidden], same dtype as x.output_scale: [E, max_token_num, kb_dim] uint8, SfAtom layout (128-byte aligned).kb_dim = hidden / 32 (both scale modes).
permuted_indice_map: [num_tokens, topk] int32, maps (token, slot) to expert-local offset.token_nums_per_expert: [E, 1] int32, count of tokens assigned to each expert.
- Return type:
Tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]
Example
from halops import prefill_permute_to_masked_gemm num_tokens, hidden, topk = 1024, 2048, 8 num_local_experts = 16 # average tokens per expert is num_tokens * topk / E = 512, so # max_token_num must leave headroom for routing imbalance max_token_num = 1024 x = torch.randn(num_tokens, hidden, dtype=torch.bfloat16, device="cuda") # broadcast mode: int32 scale, hidden / 128 / 4 columns scale = torch.zeros(num_tokens, hidden // 128 // 4, dtype=torch.int32, device="cuda") topk_ids = torch.randint(0, num_local_experts, (num_tokens, topk), dtype=torch.int64, device="cuda") permute_x, out_scale, indice_map, token_nums = ( prefill_permute_to_masked_gemm( x, scale, topk_ids, num_local_experts, max_token_num))