mobile_optimizer.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. # mypy: allow-untyped-defs
  2. """This module contains utility method for mobile model optimization and lint."""
  3. import torch
  4. from enum import Enum
  5. from torch._C import _MobileOptimizerType as MobileOptimizerType
  6. from typing import AnyStr
  7. class LintCode(Enum):
  8. BUNDLED_INPUT = 1
  9. REQUIRES_GRAD = 2
  10. DROPOUT = 3
  11. BATCHNORM = 4
  12. def optimize_for_mobile(
  13. script_module: torch.jit.ScriptModule,
  14. optimization_blocklist: set[MobileOptimizerType] | None = None,
  15. preserved_methods: list[AnyStr] | None = None,
  16. backend: str = 'CPU') -> torch.jit.RecursiveScriptModule:
  17. """
  18. Optimize a torch script module for mobile deployment.
  19. Args:
  20. script_module: An instance of torch script module with type of ScriptModule.
  21. optimization_blocklist: A set with type of MobileOptimizerType. When set is not passed,
  22. optimization method will run all the optimizer pass; otherwise, optimizer
  23. method will run the optimization pass that is not included inside optimization_blocklist.
  24. preserved_methods: A list of methods that needed to be preserved when freeze_module pass is invoked
  25. backend: Device type to use for running the result model ('CPU'(default), 'Vulkan' or 'Metal').
  26. Returns:
  27. A new optimized torch script module
  28. """
  29. if not isinstance(script_module, torch.jit.ScriptModule):
  30. raise TypeError(
  31. f'Got {type(script_module)}, but ScriptModule is expected.')
  32. if optimization_blocklist is None:
  33. optimization_blocklist = set()
  34. if preserved_methods is None:
  35. preserved_methods = []
  36. # Convert potential byte arrays into strings (if there is any) to pass type checking
  37. # Here we use a new name as assigning it back to preserved_methods will invoke
  38. # mypy errors (i.e. List[AnyStr] = List[str])
  39. preserved_methods_str: list[str] = [str(method) for method in preserved_methods]
  40. bundled_inputs_attributes = _get_bundled_inputs_preserved_attributes(script_module, preserved_methods_str)
  41. if all(hasattr(script_module, method) for method in bundled_inputs_attributes):
  42. preserved_methods_str = list(set(preserved_methods_str + bundled_inputs_attributes))
  43. non_exist_methods = [method for method in preserved_methods_str if not hasattr(script_module, method)]
  44. if non_exist_methods:
  45. raise AttributeError(
  46. f"The following methods to preserve do not exist in script_module: {', '.join(non_exist_methods)}")
  47. backend = backend.lower()
  48. if backend == 'cpu':
  49. optimized_cpp_module = torch._C._jit_pass_optimize_for_mobile(
  50. script_module._c,
  51. optimization_blocklist,
  52. preserved_methods_str)
  53. elif backend == 'vulkan':
  54. optimized_cpp_module = torch._C._jit_pass_vulkan_optimize_for_mobile(
  55. script_module._c,
  56. optimization_blocklist,
  57. preserved_methods_str)
  58. elif backend == 'metal':
  59. optimized_cpp_module = torch._C._jit_pass_metal_optimize_for_mobile(script_module._c, preserved_methods_str)
  60. else:
  61. raise TypeError("Unknown backend, must be one of 'CPU', 'Vulkan' or 'Metal'")
  62. return torch.jit._recursive.wrap_cpp_module(optimized_cpp_module)
  63. def generate_mobile_module_lints(script_module: torch.jit.ScriptModule):
  64. """
  65. Generate a list of lints for a given torch script module.
  66. Args:
  67. script_module: An instance of torch script module with type of ScriptModule.
  68. Returns:
  69. lint_map: A list of dictionary that contains modules lints
  70. """
  71. if not isinstance(script_module, torch.jit.ScriptModule):
  72. raise TypeError(
  73. f'Got {type(script_module)}, but ScriptModule is expected.')
  74. lint_list = []
  75. if not hasattr(script_module, "_generate_bundled_inputs_for_forward"):
  76. lint_list.append({"name": LintCode.BUNDLED_INPUT.name, "message": "No bundled input for forward, please add bundled inputs "
  77. "before saving the module using torch.utils.bundled_inputs.augment_model_with_bundled_inputs."})
  78. for name, param in script_module.named_parameters():
  79. if param.requires_grad:
  80. lint_list.append({"name": LintCode.REQUIRES_GRAD.name, "message": f"Param {name} requires grad, "
  81. "please set torch.no_grad() to reduce memory usage and improve computation speed during "
  82. "inference phase."})
  83. op_names = torch.jit.export_opnames(script_module)
  84. for op_name in op_names:
  85. if "dropout" in op_name:
  86. lint_list.append({"name": LintCode.DROPOUT.name,
  87. "message": f"Operator {op_name} exists, remember to call eval() before "
  88. "saving the module.and call torch.utils.mobile_optimizer.optimize_for_mobile to drop dropout "
  89. "operator."})
  90. if "batch_norm" in op_name:
  91. lint_list.append({"name": LintCode.BATCHNORM.name,
  92. "message": f"Operator {op_name} exists, remember to call eval() before "
  93. "saving the module and call torch.utils.mobile_optimizer.optimize_for_mobile to drop batch_norm "
  94. "operator."})
  95. return lint_list
  96. def _get_bundled_inputs_preserved_attributes(script_module: torch.jit.ScriptModule, preserved_methods: list[str]) -> list[str]:
  97. bundled_inputs_attributes = []
  98. # Has bundled inputs for forward
  99. if hasattr(script_module, 'get_all_bundled_inputs'):
  100. bundled_inputs_attributes.append('get_all_bundled_inputs')
  101. bundled_inputs_attributes.append('get_num_bundled_inputs')
  102. # Bundled inputs in module after the change that introduced bundled inputs for multiple functions
  103. if hasattr(script_module, 'get_bundled_inputs_functions_and_info'):
  104. bundled_inputs_attributes.append('get_bundled_inputs_functions_and_info')
  105. all_info = script_module.get_bundled_inputs_functions_and_info()
  106. for function_name in all_info:
  107. if function_name not in preserved_methods:
  108. bundled_inputs_attributes.append(function_name)
  109. bundled_inputs_attributes.append("get_all_bundled_inputs_for_" + function_name)
  110. bundled_inputs_attributes.append("_bundled_inputs_deflated_" + function_name)
  111. return bundled_inputs_attributes