Repository navigation
Conversation
[Medium impact] The reviewed changes appear safe to merge after the stated prerequisite PR. SummaryAdds experimental cuBLASLt strided batched GEMM and the fusible PyTorch operation
Diagram%%{init: {'theme': 'neutral'}}%%
flowchart LR
A[BatchedLinear] --> B[Prepare input and weight]
B --> C[PyTorch GEMM binding]
C --> D{MXFP8 inputs?}
D -->|Yes| E[Pack scales by batch]
E --> F[cuBLASLt batched GEMM]
D -->|No| F
F --> G[Add bias and restore output shape]
Reviews (12) · Last reviewed commit: "Pass 3D operands from BatchedLinear to s..." · Reviewed by Greptile |
|
/te-ci |
There was a problem hiding this comment.
Mathematically, is this equivalent to GroupedLinear with uniform split sizes (m_splits=[S*B] * G)?
If so, are there perf reasons to support a dedicated kernel? I could imagine that batched GEMM is easier to optimize than grouped GEMM, but grouped GEMM should already be getting a good fraction of the peak compute throughput. I would like to make sure we have a compelling benefit before accepting a major maintenance burden (on the order of Linear and GroupedLinear, especially if we need to support all the weird Mcore-specific integrations).
Signed-off-by: Xin Yao <xiny@nvidia.com>
Signed-off-by: Xin Yao <xiny@nvidia.com>
583e536 to
c09a325
Compare
Signed-off-by: Xin Yao <xiny@nvidia.com>
Comments Outside DiffThese findings could not be posted inline.
|
Signed-off-by: Xin Yao <xiny@nvidia.com>
for more information, see https://pre-commit.ci
| int64_t stridec, NVTETensor D, int64_t ldd, int64_t strided, | ||
| int64_t batch_count, int64_t m, int64_t n, int64_t k, | ||
| NVTETensor workspace, NVTEMatmulConfig config, | ||
| cudaStream_t stream); |
There was a problem hiding this comment.
There are a lot of redundant params here. m, n, k, lda, stridea, etc are contained in the dimensions of A and B. Also, it seems this is a general pattern that we could apply in other situation. We should avoid hard-coding this to only handle MLA.
There was a problem hiding this comment.
I kept ld/stride explicit on purpose. The main use case is an input activation stored contiguously as [S, B, G, D] with the GEMM batch dim G interleaved, i.e. ld = G*D and stride = D. NVTETensor has no strides, so this layout can't be inferred from shapes. Copying to [G, S*B, D] would defeat the purpose of the strided kernel.
m/n/k could be derived from shapes for the batch-major case, but not for the interleaved case without extra conventions. I kept them explicit so the API mirrors cuBLASLt's strided-batch API. Nothing in the API is MLA-specific; it is a generic strided batched GEMM, and the header docs now describe it that way. I'm happy to revisit this if NVTETensor gets stride support.
There was a problem hiding this comment.
Logically, the inputs to batched GEMM are 3D tensors, where the 3 dims are interpreted as the batch dim column dim, and row dim. We can figure everything out based on the batch dim and trans*:
- Batch dim tells us how to flatten to 3D.
batch_dim=0means we flatten[G, ..., D]to[G, flat, D].batch_dim=-2means we flatten[..., G, D]to[flat, G, D]. trans*tells us whether the col dim or row dim comes first.transa=Falseandtransb=Truemeans the last dim isk.
So we can make this significantly more logical and less redundant by replacing all of the stride and dim stuff with batch_dim_a and batch_dim_b.
For comparison, plain GEMM operates on 2D data and we implicitly flatten [..., D] to [flat, D]. Batched GEMM adds an extra dim, so one extra parameter seems fair.
There was a problem hiding this comment.
Done. nvte_cublas_gemm_strided_batched and nvte_cublas_gemm_strided_batched_mxfp8 now take batch_dim_a, batch_dim_b, and batch_dim_d instead of dims, leading dims, and strides. batch_dim_d applies to both C and D, since the fprop and dgrad outputs are also interleaved. Each tensor is flattened to [G, R, C] (batch_dim=0) or [R, G, C] (batch_dim=-2). m/n/k, ld, and stride are derived from the shapes and trans*, with consistency checks across A, B, and D.
One detail in the PyTorch binding: with batch_dim=-2, column-wise MXFP8 data has to be quantized as a 2D [R, G*C] view, otherwise 32-row scaling blocks would mix batch entries. The binding therefore treats 2D MXFP8 data with batch_dim=-2 as that view and takes G from the output.
Signed-off-by: Xin Yao <xiny@nvidia.com>
| int64_t stridec, NVTETensor D, int64_t ldd, int64_t strided, | ||
| int64_t batch_count, int64_t m, int64_t n, int64_t k, | ||
| NVTETensor workspace, NVTEMatmulConfig config, | ||
| cudaStream_t stream); |
There was a problem hiding this comment.
Logically, the inputs to batched GEMM are 3D tensors, where the 3 dims are interpreted as the batch dim column dim, and row dim. We can figure everything out based on the batch dim and trans*:
- Batch dim tells us how to flatten to 3D.
batch_dim=0means we flatten[G, ..., D]to[G, flat, D].batch_dim=-2means we flatten[..., G, D]to[flat, G, D]. trans*tells us whether the col dim or row dim comes first.transa=Falseandtransb=Truemeans the last dim isk.
So we can make this significantly more logical and less redundant by replacing all of the stride and dim stuff with batch_dim_a and batch_dim_b.
For comparison, plain GEMM operates on 2D data and we implicitly flatten [..., D] to [flat, D]. Batched GEMM adds an extra dim, so one extra parameter seems fair.
| _HIGH_PRECISION_DTYPES = (torch.float32, torch.float16, torch.bfloat16) | ||
|
|
||
|
|
||
| class BatchedLinear(BasicOperation): |
There was a problem hiding this comment.
We should add a fallback implementation for unsupported quantization recipes. MXFP8 support is an internal detail, and if users need to be aware of this before calling this API then it significantly reduces its usefulness (e.g. for experimental or newly-developed recipes). It's fine if the fallback is slow, but it should work.
There are two natural fallbacks:
- Apply quantizers without checking the recipe, and if it's not MXFP8 then dequantize to BF16 before calling batched GEMM (preferably in
cpp_extensions.strided_batched_gemm). This is somewhat easier since the op can use the same logic for every quantization format, but it adds unnecessary dequantize kernels. - Reorder input tensor if
batch_dim=-2, then slice. Independently call quantize and GEMM for each slice. This requires some MXFP8-awareness in the op impl, but it means the fallback impl matches what we were doing before this PR.
There was a problem hiding this comment.
I went with the first option. BatchedLinear now accepts any recipe and applies the recipe's quantizers as usual. For MXFP8, the data is quantized so its scales can be packed by batch. For other formats, the op only quantizes row-wise, and cpp_extensions.strided_batched_gemm dequantizes and runs the high-precision batched GEMM, using your suggested fallback. There is one special case: if the weight is quantized in a non-MXFP8 format but compute is MXFP8, the weight is dequantized and requantized to MXFP8. The fusible-op tests now cover the whole _quantization_list, and test_deferred_param_init_quantized includes batched_linear for all recipes.
| @pytest.mark.parametrize("quantized_weight", (False, True)) | ||
| @pytest.mark.parametrize("input_requires_grad", (False, True)) | ||
| @pytest.mark.parametrize("weight_requires_grad", (False, True)) | ||
| def test_batched_linear( |
There was a problem hiding this comment.
This has 384 test cases, which is just about manageable. If we add more parametization in the future, we should split this up into multiple subtests so that the growth rate changes from 2^n to 2*n.
There was a problem hiding this comment.
Agreed. With _quantization_list it now collects about 770 cases, though many are skipped (e.g. quantized FP32), and the batched-linear tests run in about 10 s on B200. I'll split it into subtests if we add more parametrization.
Replace the explicit m/n/k/ld/stride arguments of strided batched GEMM with per-tensor batch dims, and fall back to high-precision GEMM for quantization formats other than MXFP8. Rename BatchedLinear arguments to batch_size and input_batch_dim. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Xin Yao <xiny@nvidia.com>
Reshape BatchedLinear inputs and outputs to 3D before GEMM so that 2D MXFP8 data with batch_dim=-2 is only ever the [rows, batch*cols] view used for column-wise quantization. Only require MXFP8 row counts divisible by 32 when the weight gradient is needed, and document that 2D [batch, cols] tensors should use batch_dim=0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Xin Yao <xiny@nvidia.com>
Description
This PR adds experimental cuBLASLt-backed strided batched GEMM support and a new fusible PyTorch operation exported as
transformer_engine.pytorch.ops.BatchedLinear.The main use case is applying one weight matrix per group without rearranging interleaved activations:
Both contiguous
[G, ..., D]and[..., G, D]input layouts are supported through explicit leading dimensions and batch strides. MXFP8 scales are packed into the cuBLASLt per-batch layout internally before each GEMM.Dependency
This PR depends on #3327 for meta-device deferred initialization in the operation fuser and should be merged after it.
Type of change
Changes
transformer_engine.pytorch.ops.BatchedLinearwith high-precision and MXFP8 forward and backward computation.return_bias,save_original_input, custom initialization and RNG tracking, and main-gradient accumulation.Current limitations
[G, ..., D]or[..., G, D]layouts.Checklist