-
Notifications
You must be signed in to change notification settings - Fork 34.4k
Fix deepgemm on multiple devices #47323
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
30bbd43
671b34c
ac1a0bb
f8cb5e4
3e8646b
24414cd
57f3a84
ca026bc
17f1e5e
7badb9b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -183,7 +183,6 @@ def finegrained_fp8_linear( | |
| block_size: list[int] | None = None, | ||
| bias: torch.Tensor | None = None, | ||
| activation_scale: torch.Tensor | None = None, | ||
| output_dtype: torch.dtype | None = None, | ||
| ) -> torch.Tensor: | ||
| """Triton FP8/FP4 linear: fused act-quant + matmul, then optional bias add. | ||
|
|
||
|
|
@@ -197,7 +196,7 @@ def finegrained_fp8_linear( | |
| weight, | ||
| weight_scale_inv, | ||
| block_size, | ||
| output_dtype, | ||
| input.dtype, | ||
| activation_scale=activation_scale, | ||
| ) | ||
| if bias is not None: | ||
|
|
@@ -212,10 +211,12 @@ def fp8_linear( | |
| block_size: list[int] | None = None, | ||
| bias: torch.Tensor | None = None, | ||
| activation_scale: torch.Tensor | None = None, | ||
| output_dtype: torch.dtype | None = None, | ||
| allow_deepgemm: bool = True, | ||
| ) -> torch.Tensor: | ||
| """End-to-end FP8/FP4 linear used by `FP8Linear` and the eager `FP8Experts` loop. | ||
|
|
||
| The output dtype always follows ``input``. | ||
|
|
||
| Dispatch order — both backends handle FP8 and FP4 weights with fp32 or UE8M0 scales: | ||
| 1. DeepGEMM (`deepgemm_fp8_fp4_linear`) — 3-6× faster on the shapes it supports. | ||
| Preferred for FP4, UE8M0 SFs, and 128×128 block FP8. | ||
|
|
@@ -233,26 +234,25 @@ def fp8_linear( | |
| bias: optional bias added to the matmul output. | ||
| activation_scale: pass a per-tensor scalar to use static activation quant; leave `None` | ||
| for dynamic (per-token) quant. | ||
| output_dtype: desired output dtype. | ||
| allow_deepgemm: set ``False`` to force the Triton fallback for this call. Used when the | ||
| model spans multiple CUDA devices in one process — DeepGEMM's cached kernels are bound | ||
| to a single CUDA context and produce garbage across devices (see the multi-device guard | ||
| in ``quantizer_finegrained_fp8.py``). | ||
| """ | ||
| # DeepGEMM is CUDA-only, dynamic-only, SM90+ only, FP4/FP8-block-128-only. | ||
| # ``TRANSFORMERS_DISABLE_DEEPGEMM_LINEAR=1`` forces the Triton fallback for this single | ||
| # dispatcher (the experts ``"deepgemm"`` impl is unaffected — use ``set_experts_implementation`` | ||
| # for that). Used by the FP8 MoE batched_mm / grouped_mm paths to avoid a still-unexplained | ||
| # DeepGEMM-vs-Triton interaction that degrades end-to-end generation on B200 (per-row kernel | ||
| # outputs still measure bit-perfect, but final tokens drift; not reproducible with the | ||
| # DeepGEMM linear off). Also temporarily skipped under ``torch.compile`` — DeepGEMM's | ||
| # per-token cast calls ``pack_ue8m0_to_int`` which has data-dependent bit-twiddling that | ||
| # dynamo can't guard. TODO: remove the ``is_torchdynamo_compiling`` gate once the upstream | ||
| # ``pack_ue8m0_to_int`` is rewritten to be FakeTensor-friendly; the Triton fallback is | ||
| # dynamo-friendly today via its ``@triton_op`` registration. | ||
|
Comment on lines
-244
to
-248
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this was already fixed
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah nice, is it already also the correct version we use and we just forgot?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes i already submitted the fix as part of the dsv4 static cache PR but that never got merged (perf not so great for now) |
||
| # DeepGEMM linear off). | ||
| deepgemm_preferred = ( | ||
| activation_scale is None | ||
| allow_deepgemm | ||
| and activation_scale is None | ||
| and weight.device.type == "cuda" | ||
| and torch.cuda.get_device_properties().major >= 9 | ||
| and (weight.dtype == torch.int8 or (block_size is not None and block_size[0] == block_size[1] == 128)) | ||
| and os.environ.get("TRANSFORMERS_DISABLE_DEEPGEMM_LINEAR", "0") != "1" | ||
| and not is_torchdynamo_compiling() | ||
| ) | ||
|
|
||
| if deepgemm_preferred: | ||
|
|
@@ -262,7 +262,6 @@ def fp8_linear( | |
| weight, | ||
| weight_scale_inv, | ||
| block_size=block_size, | ||
| output_dtype=output_dtype, | ||
| activation_scale=activation_scale, | ||
| bias=bias, | ||
| ) | ||
|
|
@@ -274,10 +273,14 @@ def fp8_linear( | |
| "Set `TRANSFORMERS_DISABLE_DEEPGEMM_LINEAR=1` to skip DeepGEMM for FP8 linear entirely." | ||
| ) | ||
|
|
||
| return finegrained_fp8_linear(input, weight, weight_scale_inv, block_size, bias, activation_scale, output_dtype) | ||
| return finegrained_fp8_linear(input, weight, weight_scale_inv, block_size, bias, activation_scale) | ||
|
|
||
|
|
||
| class FP8Linear(nn.Linear): | ||
| # Set True at load when the model spans >1 CUDA device in one process; DeepGEMM's | ||
| # context-bound kernels corrupt across devices (see `quantizer_finegrained_fp8.py`). | ||
| _deepgemm_disabled = False | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit, would set on init either way, no?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. no only when we have than one device do we update it, i can remove this but will have to do getattr and it will be there sometimes and sometimes not so i thought it could make sense to add it like the _can_compile_fullgraph and other capability flags
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we clarify the comment a bit that this is temporary and will be removed after this is fixed upstream in the kernel
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. are you sure this is fixable or will be fixed upstream ? imo there's no guarantee tbh
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm hopeful at least 😆 this cannot be intended behavior but fine with keeping te comment as is as well. More of a nit
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i actually tried before and failed 😔 the problem is deep in their jit compilation stack 🥲
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Damn, we gotta wait for upstream fixes then :( |
||
|
|
||
| def __init__( | ||
| self, | ||
| in_features: int, | ||
|
|
@@ -329,8 +332,8 @@ def forward(self, input: torch.Tensor) -> torch.Tensor: | |
| scale_inv, | ||
| block_size=self.block_size, | ||
| activation_scale=self.activation_scale, | ||
| output_dtype=input.dtype, | ||
| bias=self.bias, | ||
| allow_deepgemm=not self._deepgemm_disabled, | ||
| ) | ||
|
|
||
|
|
||
|
|
@@ -574,6 +577,10 @@ def fp8_grouped_mm_experts_forward( | |
|
|
||
|
|
||
| class FP8Experts(nn.Module): | ||
| # Set True at load when the model spans >1 CUDA device in one process; DeepGEMM's | ||
| # context-bound kernels corrupt across devices (see `quantizer_finegrained_fp8.py`). | ||
| _deepgemm_disabled = False | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same |
||
|
|
||
| # Per-`_experts_implementation` rewrite of parallel-layer kinds in the TP/EP plan. | ||
| # The plan dicts store `{module-path-pattern: parallel-layer-kind}`; this maps an | ||
| # old kind to a new kind, and the quantizer rewrites every plan VALUE that matches. | ||
|
|
@@ -729,7 +736,7 @@ def linear( | |
| weight_scale_inv, | ||
| self.block_size, | ||
| activation_scale=activation_scale, | ||
| output_dtype=input.dtype, | ||
| allow_deepgemm=not self._deepgemm_disabled, | ||
| ) | ||
|
|
||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this was just bad because it defaulted to None and deepgemm and fp8 don't behave the same on None output dtype anyways
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we add * at least to be not BC in case anyone used the interface? Similar to others, it is slightly breaking because an output dtype is no longer possible
Maybe we could default to input dtype on None instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
defaulted to input.dtype but i think we should deprecate it, in general linears don't really interface an output dtype, see torch's linear https://docs.pytorch.org/docs/2.13/generated/torch.nn.functional.linear.html