|
9 | 9 | os.environ.setdefault("HF_HUB_DISABLE_XET", "1") |
10 | 10 |
|
11 | 11 | import torch |
| 12 | +from executorch.backends.arm.quantizer import ( |
| 13 | + get_symmetric_quantization_config, |
| 14 | + TOSAQuantizer, |
| 15 | +) |
12 | 16 | from executorch.backends.arm.scripts.neural_graphics_test_data import ( |
13 | 17 | _NFRU_INPUT_CHANNELS, |
14 | 18 | iter_nfru_test_calibration_samples, |
|
26 | 30 | TosaPipelineINT, |
27 | 31 | VgfPipeline, |
28 | 32 | ) |
| 33 | +from executorch.backends.arm.tosa import TosaSpecification |
| 34 | +from executorch.backends.transforms.duplicate_dynamic_quant_chain import ( |
| 35 | + DuplicateDynamicQuantChainPass, |
| 36 | +) |
29 | 37 | from huggingface_hub import hf_hub_download |
30 | 38 | from ng_model_gym.usecases.nfru.model.nfru_v1_nn import ( # type: ignore[import-not-found,import-untyped] |
31 | 39 | NFRUAutoEncoder, |
32 | 40 | ) |
| 41 | +from torchao.quantization.pt2e import ( |
| 42 | + allow_exported_model_train_eval, |
| 43 | + move_exported_model_to_eval, |
| 44 | +) |
| 45 | +from torchao.quantization.pt2e.quantize_pt2e import convert_pt2e, prepare_qat_pt2e |
33 | 46 |
|
34 | 47 | input_t = Tuple[torch.Tensor] # Input x |
35 | 48 |
|
@@ -60,6 +73,57 @@ def nfru() -> NFRUAutoEncoder: |
60 | 73 | return model |
61 | 74 |
|
62 | 75 |
|
| 76 | +def prequantized_nfru(inputs: input_t) -> torch.fx.GraphModule: |
| 77 | + weights = hf_hub_download( # nosec B615 |
| 78 | + repo_id="Arm/neural-frame-rate-upscaling", |
| 79 | + filename="nfru_v1_int8.pt", |
| 80 | + revision="main", |
| 81 | + ) |
| 82 | + checkpoint = torch.load( |
| 83 | + weights, |
| 84 | + map_location=torch.device("cpu"), |
| 85 | + weights_only=True, |
| 86 | + )["model_state_dict"] |
| 87 | + prefix = "network.auto_encoder." |
| 88 | + assert all(key.startswith(prefix) for key in checkpoint) |
| 89 | + state_dict = {key.removeprefix(prefix): value for key, value in checkpoint.items()} |
| 90 | + |
| 91 | + exported = torch.export.export(nfru().eval(), inputs, strict=True).module() |
| 92 | + quantizer = TOSAQuantizer(TosaSpecification.create_from_string("TOSA-1.0+INT")) |
| 93 | + quantizer.set_global( |
| 94 | + get_symmetric_quantization_config(is_per_channel=False, is_qat=True) |
| 95 | + ) |
| 96 | + prepared = prepare_qat_pt2e(exported, quantizer) |
| 97 | + |
| 98 | + parameter_keys = list(dict(prepared.named_parameters())) |
| 99 | + lifted_parameter_keys = [ |
| 100 | + key for key in state_dict if key.startswith("_param_constant") |
| 101 | + ] |
| 102 | + buffer_keys = [ |
| 103 | + key |
| 104 | + for key, _ in prepared.named_buffers() |
| 105 | + if not key.startswith("activation_post_process_") |
| 106 | + ] |
| 107 | + lifted_buffer_keys = [ |
| 108 | + key for key in state_dict if key.startswith("_tensor_constant") |
| 109 | + ] |
| 110 | + assert len(parameter_keys) == len(lifted_parameter_keys) |
| 111 | + assert len(buffer_keys) == len(lifted_buffer_keys) |
| 112 | + for lifted_key, parameter_key in zip( |
| 113 | + lifted_parameter_keys, parameter_keys, strict=True |
| 114 | + ): |
| 115 | + state_dict[parameter_key] = state_dict.pop(lifted_key) |
| 116 | + for lifted_key, buffer_key in zip(lifted_buffer_keys, buffer_keys, strict=True): |
| 117 | + state_dict[buffer_key] = state_dict.pop(lifted_key) |
| 118 | + |
| 119 | + prepared.load_state_dict(state_dict, strict=True) |
| 120 | + move_exported_model_to_eval(prepared) |
| 121 | + converted = convert_pt2e(prepared) |
| 122 | + DuplicateDynamicQuantChainPass()(converted) |
| 123 | + allow_exported_model_train_eval(converted) |
| 124 | + return converted |
| 125 | + |
| 126 | + |
63 | 127 | def example_inputs(): |
64 | 128 | return load_nfru_verification_inputs() |
65 | 129 |
|
@@ -119,6 +183,28 @@ def test_nfru_tosa_INT(use_real_data, is_qat): |
119 | 183 | pipeline.run() |
120 | 184 |
|
121 | 185 |
|
| 186 | +@common.parametrize("use_real_data", input_test_data) |
| 187 | +def test_nfru_prequantized_tosa_INT(use_real_data): |
| 188 | + inputs = example_inputs() if use_real_data else random_inputs() |
| 189 | + pipeline = TosaPipelineINT[input_t]( |
| 190 | + prequantized_nfru(inputs), |
| 191 | + inputs, |
| 192 | + aten_op=[], |
| 193 | + exir_op=[], |
| 194 | + atol=0.2, |
| 195 | + qtol=2 if use_real_data else 1, |
| 196 | + ) |
| 197 | + pipeline.pop_stage("quantize") |
| 198 | + pipeline.pop_stage("check.quant_nodes") |
| 199 | + pipeline.add_stage_after( |
| 200 | + "export", |
| 201 | + pipeline.tester.check, |
| 202 | + ["torch.ops.quantized_decomposed.dequantize_per_tensor.default"], |
| 203 | + suffix="prequant_nodes", |
| 204 | + ) |
| 205 | + pipeline.run() |
| 206 | + |
| 207 | + |
122 | 208 | @common.parametrize("use_real_data", input_test_data) |
123 | 209 | def test_nfru_tosa_INT_a16w8(use_real_data): |
124 | 210 | pipeline = TosaPipelineINT[input_t]( |
@@ -179,6 +265,31 @@ def test_nfru_vgf_quant(use_real_data, is_qat): |
179 | 265 | pipeline.run() |
180 | 266 |
|
181 | 267 |
|
| 268 | +@common.SkipIfNoModelConverter |
| 269 | +@common.parametrize("use_real_data", input_test_data) |
| 270 | +def test_nfru_prequantized_vgf_INT(use_real_data): |
| 271 | + inputs = example_inputs() if use_real_data else random_inputs() |
| 272 | + pipeline = VgfPipeline[input_t]( |
| 273 | + prequantized_nfru(inputs), |
| 274 | + inputs, |
| 275 | + aten_op=[], |
| 276 | + exir_op=[], |
| 277 | + tosa_version="TOSA-1.0+INT", |
| 278 | + quantize=True, |
| 279 | + atol=0.2, |
| 280 | + qtol=2 if use_real_data else 1, |
| 281 | + ) |
| 282 | + pipeline.pop_stage("quantize") |
| 283 | + pipeline.pop_stage("check.quant_nodes") |
| 284 | + pipeline.add_stage_after( |
| 285 | + "export", |
| 286 | + pipeline.tester.check, |
| 287 | + ["torch.ops.quantized_decomposed.dequantize_per_tensor.default"], |
| 288 | + suffix="prequant_nodes", |
| 289 | + ) |
| 290 | + pipeline.run() |
| 291 | + |
| 292 | + |
182 | 293 | @common.SkipIfNoModelConverter |
183 | 294 | @common.parametrize("use_real_data", input_test_data) |
184 | 295 | def test_nfru_vgf_quant_a16w8(use_real_data): |
|
0 commit comments