Repository navigation
Keep GatedDeltaNet beta / alpha gates unquantized in Megatron fake quant #3124
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 all commits
12948e0
9227de6
0c2ee59
0338cf9
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 |
|---|---|---|
|
|
@@ -26,6 +26,7 @@ | |
| import transformer_engine.pytorch.module.linear as te_linear | ||
|
|
||
| from modelopt.torch.quantization.utils import replace_function | ||
| from modelopt.torch.quantization.utils.core_utils import apply_without_qdq | ||
|
|
||
| from ..nn import GroupedQuantizer, QuantModuleRegistry, SequentialQuantizer, TensorQuantizer | ||
| from .custom import _ParallelLinear | ||
|
|
@@ -315,19 +316,41 @@ class _QuantLayerNormLinearFunc(torch.autograd.Function): | |
|
|
||
| @staticmethod | ||
| def forward(ctx, inp, ln_weight, ln_bias, weight, *args, **kwargs): | ||
| input_quantizer, weight_quantizer = _QuantLayerNormLinearFunc.modelopt_quantizers | ||
| input_quantizer, weight_quantizer, unquantized_rows = ( | ||
| _QuantLayerNormLinearFunc.modelopt_quantizers | ||
| ) | ||
|
|
||
| qweight = weight_quantizer(weight) | ||
| if unquantized_rows is not None: | ||
| # Deployment keeps these rows in BF16: they skip QDQ but keep the quantizers' scaling | ||
| # and rotation. Not in place: the fake-quant weight cache may hold qweight. | ||
| row_weight = apply_without_qdq(weight_quantizer, weight[unquantized_rows]) | ||
| qweight = torch.cat( | ||
| ( | ||
| qweight[: unquantized_rows.start], | ||
| row_weight.to(qweight.dtype), | ||
| qweight[unquantized_rows.stop :], | ||
| ) | ||
| ) | ||
| qweight.requires_grad = weight.requires_grad | ||
| if ctx is not None: | ||
| # We need to recompute the quantized input for the backward pass, so we save the input_quantizer | ||
| ctx.modelopt_input_quantizer = input_quantizer | ||
| ctx.modelopt_unquantized_rows = unquantized_rows | ||
|
|
||
| original_gemm = te_layernorm_linear.general_gemm | ||
|
|
||
| def _patched_general_gemm(weight, input, *gemm_args, **gemm_kwargs): | ||
| def _patched_general_gemm(gemm_weight, input, *gemm_args, **gemm_kwargs): | ||
| qinput = input_quantizer(input) | ||
| return original_gemm(weight, qinput, *gemm_args, **gemm_kwargs) | ||
| outputs = original_gemm(gemm_weight, qinput, *gemm_args, **gemm_kwargs) | ||
| if unquantized_rows is not None: | ||
| bias = gemm_kwargs.get("bias") | ||
| outputs[0][..., unquantized_rows] = torch.nn.functional.linear( | ||
| apply_without_qdq(input_quantizer, input).to(row_weight.dtype), | ||
| row_weight, | ||
| None if bias is None else bias[unquantized_rows], | ||
| ).to(outputs[0].dtype) | ||
| return outputs | ||
|
|
||
| with replace_function( | ||
| te_layernorm_linear, | ||
|
|
@@ -366,7 +389,16 @@ def _patched_general_gemm(a, b, *gemm_args, **gemm_kwargs): | |
| # but gemm is called with the unquantized input (a) | ||
| # So lets first get the quantized input (qinput) and then call the gemm | ||
| qinput = ctx.modelopt_input_quantizer(a) | ||
| return original_gemm(qinput, b, *gemm_args, **gemm_kwargs) | ||
| outputs = original_gemm(qinput, b, *gemm_args, **gemm_kwargs) | ||
| rows = ctx.modelopt_unquantized_rows | ||
| if rows is not None and outputs[0] is not None: | ||
| # The forward computed these rows from the unquantized input. | ||
| x = apply_without_qdq(ctx.modelopt_input_quantizer, a) | ||
| dy = b[..., rows].reshape(-1, rows.stop - rows.start) | ||
| outputs[0][rows] = (dy.t() @ x.reshape(dy.shape[0], -1).to(dy.dtype)).to( | ||
| outputs[0].dtype | ||
|
Contributor
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.
[P2] The gate branch now preserves transforms in its forward values, but its backward still needs their derivatives. For example, set the input pre-quant scale to a constant 2 and divide the stored weight by 2, as the regression does: gate output matches BF16, but the unchanged first dgrad GEMM computes
Collaborator
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 isn't specific to the gate rows. The gate wgrad is already correct for a pre-quant scale: it is taken with respect to the stored weight Making the TE LayerNormLinear backward transform-aware (input scale / rotation VJP in dgrad, weight-transform VJP in wgrad) for all rows is a separate change to AWQ / SmoothQuant / rotation QAT on TE layers. It's out of scope for this fix, so I'll track it as a follow-up rather than special-case the gate rows here. |
||
| ) | ||
| return outputs | ||
|
|
||
| with replace_function( | ||
| te_layernorm_linear, | ||
|
|
@@ -378,6 +410,7 @@ def _patched_general_gemm(a, b, *gemm_args, **gemm_kwargs): | |
| outputs = te_layernorm_linear._LayerNormLinear.backward(ctx, *grad_outputs) | ||
|
|
||
| delattr(ctx, "modelopt_input_quantizer") | ||
| delattr(ctx, "modelopt_unquantized_rows") | ||
| return outputs | ||
|
|
||
|
|
||
|
|
@@ -394,13 +427,26 @@ def _setup(self): | |
| ) | ||
| self.fuse_wgrad_accumulation = False | ||
|
|
||
| def fold_weight(self, keep_attrs: bool = False): | ||
| """Fold the weight quantizer, keeping the rows the forward pass leaves unquantized.""" | ||
| rows = getattr(self, "_modelopt_unquantized_output_rows", None) | ||
|
kevalmorabia97 marked this conversation as resolved.
Resolved
|
||
| unquantized = None | ||
| if rows is not None: | ||
| with torch.no_grad(): | ||
| # Cloned: without transforms this returns a view of the weight that folding overwrites. | ||
| unquantized = apply_without_qdq(self.weight_quantizer, self.weight[rows]).clone() | ||
| super().fold_weight(keep_attrs) | ||
| if unquantized is not None: | ||
| self.weight.data[rows] = unquantized.to(self.weight.dtype) | ||
|
|
||
| def forward(self, *args, **kwargs): | ||
| """Call ModelOpt patch for _LayerNormLinear functional.""" | ||
| _assert_te_fp8_enabled() | ||
| # This is multi-process safe (such as in torch distributed jobs), not multi-thread safe | ||
| _QuantLayerNormLinearFunc.modelopt_quantizers = ( | ||
| self.input_quantizer, | ||
| self.weight_quantizer, | ||
| getattr(self, "_modelopt_unquantized_output_rows", None), | ||
| ) | ||
| with replace_function( | ||
| te_layernorm_linear, | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.