You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 4 Next »

Problem Statement

Users want to bring a FP32 model to convert it to a mixed precision model to run inference on it. They want to use model zoo to convert pretrained models in Python and other frontends. They can somewhat achieve by casting the inputs and the blocks but the same cannot be done for symbolic models (json and params). Proposing to add API to convert FP32 models to FP16.

There is some nice ongoing work to add automatic mixed precision support for training to mxnet[1]. Among other things, it automatically
adds cast layers, for conversion to FP16 or FP32 based on the operator. There are specific operator lists maintained for ops that should always run in FP16, ops that should always run in FP32 and op which should run in FP16 or FP32 based on whichever is the widest type among its inputs. It also takes into account, operators should run in specific precision only if a condition is met (for example Activation with act_type as softrelu).

I think we can use some of the ideas from AMP, to add an API to convert a model to mixed precision model and add it under AMP namespace.I elaborate more on the proposal below. I start with API changes and backend changes followed by a small section on API changes in gluon to make it easier to convert to mixed precision models.

API Addition


def convert_model(sym, arg_params, aux_params, target_dtype="float16", target_dtype_ops=None,
fp32_ops=None, widest_dtype_ops=None,
conditional_fp32_ops=None, excluded_sym_names=None):
"""API for converting a model from FP32 model to a mixed precision model.
MXNet tries to convert the FP32 model to mixed precision model by adding
cast layers using amp_cast and amp_multicast operators. The decision on
which cast layer to add is based on hardcoded lists for Automatic Mixed Precision
in MXNet. These lists can be overridden by the user by providing their own lists
using : targe_precision_ops, fp32_ops, widest_precision_ops, conditional_fp32_ops

Parameters
----------
sym : str or Symbol
Defines the structure of a neural network for FP32 types.
arg_params : dict
Dictionary of name to `NDArray`.
aux_params : dict
Dictionary of name to `NDArray`.
target_dtype : str
Currently only supports float16. The target dtype indicates to add cast layers
when possible so that lower precision computation can be leveraged.
target_dtype_ops : list of strs
Override the list of operator names casted to target_dtype.
If None, uses the framework's default list to be casted to target dtype.
fp32_ops : list of strs
Override the lists of operator names casted to FP32.
If None, uses the framework's default list to be casted to FP32.
widest_dtype_ops : list of strs
A list of op names provided by user which should run in widest precision among its inputs.
If None, uses the framework's default list of widest_precision_ops.
conditional_fp32_ops : list of (string, string, list of string)
Override the list of operators to be casted to FP32.
The format of the list is
(name of the function, name of the parameter,
list of values of the parameter that make the operator to be casted to
fp32)
excluded_sym_names : list of strs
A list of strings that represent the names of symbols that users want to exclude
from being quantized.



target_dtype should decide which lists need to be overridden.
For example, in the future bfloat16 support may be added in which case these lists for operators running in bfloat16 will also be added to AMP.
In this case, target_dtype will allow users to choose the right dtype for the mixed precision model.

Backend Changes

Add a NNVM pass for the backend. This would use the amp lists based on the original_dtype and target_dtype.
This pass will perform graph traversal and add amp_cast and amp_multicast layers for FP16 and FP32 ops based on the op whitelists and excluded_sym_names. Some of the ideas have been borrowed from quantization pass added as part of quantization support [2].

Outline of algorithm:


1. Three additional data structures used:

  1. map from a node to a copy node in the casted graph (mirror_map)
  2. map from an input entry to the corresponding fp32 casted entry (mirror_entry_fp32_map)
  3. map from an input entry to the target dtype (e.g. fp16) casted entry (mirror_entry_target_map) (please see below fig for why b and c are needed)

Consider the below script:


data = mx.sym.var("data")
x = mx.sym.cos(data)
x2 = mx.sym.sin(data)
x3 = mx.sym.exp(data)
x4 = mx.sym.sqrt(data)
result = x + x2 + x3 + x4
casted_result = mx.contrib.amp._convert_symbol(result, target_dtype="float16",
target_dtype_ops=["sin", "cos"], fp32_ops=["exp", "sqrt"])

   

Without the mirror_entry_target_map there would have been 3 cast nodes instead of 2: 1 for amp_cast float16 and two others going as amp_casted fp32 input to exp0 and sqrt0. Thus, the two additional data structures help optimize and reuse such commonly used cast operators.


2. Visit nodes of the graph in a topologically sorted order and when each node is visited do the following:

  1. Create a copy node
  2. Clear inputs of the copy node
  3.  
    1. If node is a variable:
      1. Add mapping of the node to the copy node.
    2. Else if node is not in any whitelist:
      1. Find all inputs of original node and find corresponding mirror entry and add as inputs to the copy node.
    3. Else if node's op_name is in the fp32 whitelist:
      1. Iterate through inputs: If input is already in mirror_entry_fp32_map, add it to the copy node inputs.                                                                         If not, insert a cast node between copy_node and mirror node of previous node.                                                                 Add mapping from input node to fp32 cast node in mirror_entry_fp32_map.
    4. Else if node's op_name is in the target dtype whitelist:
      1. Iterate through inputs:
        1. If input is already in mirror_entry_target_map, add it to the copy node inputs.
        2. If not, insert a cast node between copy_node and mirror_node of previous node. Add mapping from input node to target dtype cast node in mirror_entry_target_map.
    5. Else if node's op_name is in the target dtype whitelist:
      1. Iterate through inputs:
        1. If input is already in mirror_entry_target_map, add it to the copy node inputs.
        2. If not, insert a cast node between copy_node and mirror_node of previous node. Add mapping from input node to target dtype cast node in mirror_entry_target_map.
    6. Else if node's op_name is in the widest_precision_ops whitelist:
      1. Add amp_multicast between mirror of node's inputs and copy of current node.
  4. Add mapping from node to copy node.

3. Create a new graph using the copy nodes with the node to copy node mapping.

4. Return the newly created graph. 

Example Usage


import mxnet as mx

# Simple demo model
data = mx.sym.var("data")
data2 = mx.sym.var("data2")
data3 = mx.sym.var("data3")
x = mx.sym.exp(data)
x2 = mx.sym.sin(data)
x3 = mx.sym.cos(data)
sym = x + x2 + x3
result = mx.sym.add_n(sym, data2, data3)
casted_result = mx.contrib.amp._convert_symbol(result, target_dtype="float16",
target_dtype_ops=["sin", "cos", "exp"], fp32_ops=["elemwise_add"],
widest_dtype_ops=["add_n"], conditional_fp32_ops=None)

Before/After Conversion:

                                                                                                                                                                                                                                                                         

As you can see above the converted graph has amp_cast, amp_multicast nodes which allow for appropriate casting of inputs.

Frontend Bindings

Need to add amp convert_model API support for different bindings like C++, Scala etc. 

Gluon User Experience Improvement

TO BE ADDED

References

  1. https://github.com/apache/incubator-mxnet/pull/14173
  2. https://github.com/apache/incubator-mxnet/pull/9552
  • No labels