Skip to content

Commit e091da8

Browse files
ethansfngfacebook-github-bot
authored andcommitted
Generalize QuantizedOutputWrapper for multi-output models
Summary: Add support for multiple outputs in quantized output wrapper Differential Revision: D107429509
1 parent 22a2daf commit e091da8

1 file changed

Lines changed: 72 additions & 9 deletions

File tree

backends/cadence/aot/compiler_funcs.py

Lines changed: 72 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,47 @@ def extract_output_dequant_params(
159159
raise ValueError("Could not find dequantize_per_tensor at the output of the graph")
160160

161161

162+
def extract_all_output_dequant_params(
163+
module: torch.fx.GraphModule,
164+
) -> list[QuantArgs | None]:
165+
"""
166+
Extract per-output dequantization parameters from a multi-output model.
167+
168+
Returns a QuantArgs tuple for outputs ending in dequantize_per_tensor
169+
or None for outputs that aren't dequantized.
170+
"""
171+
for node in module.graph.nodes:
172+
if node.op != "output":
173+
continue
174+
output_args = node.args[0]
175+
if not isinstance(output_args, (tuple, list)):
176+
output_args = (output_args,)
177+
params: list[QuantArgs | None] = []
178+
for out in output_args:
179+
if isinstance(out, torch.fx.Node) and "dequantize_per_tensor" in str(
180+
out.target
181+
):
182+
args = out.args[1:]
183+
if len(args) >= 5:
184+
dtype = args[4]
185+
assert isinstance(dtype, torch.dtype)
186+
params.append(
187+
(
188+
float(args[0]),
189+
int(args[1]),
190+
int(args[2]),
191+
int(args[3]),
192+
dtype,
193+
)
194+
)
195+
else:
196+
params.append(None)
197+
else:
198+
params.append(None)
199+
return params
200+
raise ValueError("No output node in graph")
201+
202+
162203
def extract_output_dequant_params_through_permute(
163204
module: torch.fx.GraphModule,
164205
) -> QuantArgs:
@@ -400,33 +441,55 @@ def sink_dequants(program: torch.export.ExportedProgram) -> None:
400441

401442
class QuantizedOutputWrapper(torch.nn.Module):
402443
"""
403-
Wrapper that quantizes a model's output so it produces uint8 tensors.
444+
Wrapper that quantizes a model's output(s) so they produce quantized tensors.
404445
405446
Mirrors QuantizedInputWrapper: the wrapper adds a quantize_per_tensor after
406-
the model's output. When the graph is traced, the dequant (from the model) →
447+
each output. When the graph is traced, the dequant (from the model) →
407448
quant (from the wrapper) pair with matching parameters folds away, leaving
408449
the output in its quantized form.
409450
410451
Args:
411452
module: The module to wrap (may already be a QuantizedInputWrapper).
412-
output_quant_args: (scale, zero_point, qmin, qmax, dtype) for the output.
453+
output_quant_args: Quantization parameters — either a single QuantArgs
454+
tuple or a list with one entry per output.
413455
"""
414456

415457
def __init__(
416458
self,
417459
module: torch.nn.Module,
418-
output_quant_args: QuantArgs,
460+
output_quant_args: Union[QuantArgs, list[QuantArgs | None]],
419461
) -> None:
420462
super().__init__()
421463
self.module: torch.nn.Module = module
422-
self.output_quant_args: QuantArgs = output_quant_args
464+
if isinstance(output_quant_args, list):
465+
self._multi_output: bool = True
466+
self._per_output_args: list[QuantArgs | None] = output_quant_args
467+
else:
468+
self._multi_output = False
469+
self._per_output_args = [output_quant_args]
423470

424471
def forward(self, *args: torch.Tensor) -> Any:
425472
result = self.module(*args)
426-
scale, zp, qmin, qmax, dtype = self.output_quant_args
427-
return torch.ops.quantized_decomposed.quantize_per_tensor.default(
428-
result, scale, zp, qmin, qmax, dtype
429-
)
473+
if not self._multi_output:
474+
qa = self._per_output_args[0]
475+
assert qa is not None
476+
scale, zp, qmin, qmax, dtype = qa
477+
return torch.ops.quantized_decomposed.quantize_per_tensor.default(
478+
result, scale, zp, qmin, qmax, dtype
479+
)
480+
out: list[torch.Tensor] = []
481+
for i, r in enumerate(result):
482+
qa = self._per_output_args[i] if i < len(self._per_output_args) else None
483+
if qa is None:
484+
out.append(r)
485+
continue
486+
scale, zp, qmin, qmax, dtype = qa
487+
out.append(
488+
torch.ops.quantized_decomposed.quantize_per_tensor.default(
489+
r, scale, zp, qmin, qmax, dtype
490+
)
491+
)
492+
return tuple(out)
430493

431494

432495
def _get_transparent_ops() -> set[Any]:

0 commit comments

Comments
 (0)