| | |
| | import copy |
| | import logging |
| | import os |
| | import torch |
| | from caffe2.proto import caffe2_pb2 |
| | from torch import nn |
| |
|
| | from detectron2.config import CfgNode |
| | from detectron2.utils.file_io import PathManager |
| |
|
| | from .caffe2_inference import ProtobufDetectionModel |
| | from .caffe2_modeling import META_ARCH_CAFFE2_EXPORT_TYPE_MAP, convert_batched_inputs_to_c2_format |
| | from .shared import get_pb_arg_vali, get_pb_arg_vals, save_graph |
| |
|
| | __all__ = [ |
| | "Caffe2Model", |
| | "Caffe2Tracer", |
| | ] |
| |
|
| |
|
| | class Caffe2Tracer: |
| | """ |
| | Make a detectron2 model traceable with Caffe2 operators. |
| | This class creates a traceable version of a detectron2 model which: |
| | |
| | 1. Rewrite parts of the model using ops in Caffe2. Note that some ops do |
| | not have GPU implementation in Caffe2. |
| | 2. Remove post-processing and only produce raw layer outputs |
| | |
| | After making a traceable model, the class provide methods to export such a |
| | model to different deployment formats. |
| | Exported graph produced by this class take two input tensors: |
| | |
| | 1. (1, C, H, W) float "data" which is an image (usually in [0, 255]). |
| | (H, W) often has to be padded to multiple of 32 (depend on the model |
| | architecture). |
| | 2. 1x3 float "im_info", each row of which is (height, width, 1.0). |
| | Height and width are true image shapes before padding. |
| | |
| | The class currently only supports models using builtin meta architectures. |
| | Batch inference is not supported, and contributions are welcome. |
| | """ |
| |
|
| | def __init__(self, cfg: CfgNode, model: nn.Module, inputs): |
| | """ |
| | Args: |
| | cfg (CfgNode): a detectron2 config used to construct caffe2-compatible model. |
| | model (nn.Module): An original pytorch model. Must be among a few official models |
| | in detectron2 that can be converted to become caffe2-compatible automatically. |
| | Weights have to be already loaded to this model. |
| | inputs: sample inputs that the given model takes for inference. |
| | Will be used to trace the model. For most models, random inputs with |
| | no detected objects will not work as they lead to wrong traces. |
| | """ |
| | assert isinstance(cfg, CfgNode), cfg |
| | assert isinstance(model, torch.nn.Module), type(model) |
| |
|
| | |
| | C2MetaArch = META_ARCH_CAFFE2_EXPORT_TYPE_MAP[cfg.MODEL.META_ARCHITECTURE] |
| | self.traceable_model = C2MetaArch(cfg, copy.deepcopy(model)) |
| | self.inputs = inputs |
| | self.traceable_inputs = self.traceable_model.get_caffe2_inputs(inputs) |
| |
|
| | def export_caffe2(self): |
| | """ |
| | Export the model to Caffe2's protobuf format. |
| | The returned object can be saved with its :meth:`.save_protobuf()` method. |
| | The result can be loaded and executed using Caffe2 runtime. |
| | |
| | Returns: |
| | :class:`Caffe2Model` |
| | """ |
| | from .caffe2_export import export_caffe2_detection_model |
| |
|
| | predict_net, init_net = export_caffe2_detection_model( |
| | self.traceable_model, self.traceable_inputs |
| | ) |
| | return Caffe2Model(predict_net, init_net) |
| |
|
| | def export_onnx(self): |
| | """ |
| | Export the model to ONNX format. |
| | Note that the exported model contains custom ops only available in caffe2, therefore it |
| | cannot be directly executed by other runtime (such as onnxruntime or TensorRT). |
| | Post-processing or transformation passes may be applied on the model to accommodate |
| | different runtimes, but we currently do not provide support for them. |
| | |
| | Returns: |
| | onnx.ModelProto: an onnx model. |
| | """ |
| | from .caffe2_export import export_onnx_model as export_onnx_model_impl |
| |
|
| | return export_onnx_model_impl(self.traceable_model, (self.traceable_inputs,)) |
| |
|
| | def export_torchscript(self): |
| | """ |
| | Export the model to a ``torch.jit.TracedModule`` by tracing. |
| | The returned object can be saved to a file by ``.save()``. |
| | |
| | Returns: |
| | torch.jit.TracedModule: a torch TracedModule |
| | """ |
| | logger = logging.getLogger(__name__) |
| | logger.info("Tracing the model with torch.jit.trace ...") |
| | with torch.no_grad(): |
| | return torch.jit.trace(self.traceable_model, (self.traceable_inputs,)) |
| |
|
| |
|
| | class Caffe2Model(nn.Module): |
| | """ |
| | A wrapper around the traced model in Caffe2's protobuf format. |
| | The exported graph has different inputs/outputs from the original Pytorch |
| | model, as explained in :class:`Caffe2Tracer`. This class wraps around the |
| | exported graph to simulate the same interface as the original Pytorch model. |
| | It also provides functions to save/load models in Caffe2's format.' |
| | |
| | Examples: |
| | :: |
| | c2_model = Caffe2Tracer(cfg, torch_model, inputs).export_caffe2() |
| | inputs = [{"image": img_tensor_CHW}] |
| | outputs = c2_model(inputs) |
| | orig_outputs = torch_model(inputs) |
| | """ |
| |
|
| | def __init__(self, predict_net, init_net): |
| | super().__init__() |
| | self.eval() |
| | self._predict_net = predict_net |
| | self._init_net = init_net |
| | self._predictor = None |
| |
|
| | __init__.__HIDE_SPHINX_DOC__ = True |
| |
|
| | @property |
| | def predict_net(self): |
| | """ |
| | caffe2.core.Net: the underlying caffe2 predict net |
| | """ |
| | return self._predict_net |
| |
|
| | @property |
| | def init_net(self): |
| | """ |
| | caffe2.core.Net: the underlying caffe2 init net |
| | """ |
| | return self._init_net |
| |
|
| | def save_protobuf(self, output_dir): |
| | """ |
| | Save the model as caffe2's protobuf format. |
| | It saves the following files: |
| | |
| | * "model.pb": definition of the graph. Can be visualized with |
| | tools like `netron <https://github.com/lutzroeder/netron>`_. |
| | * "model_init.pb": model parameters |
| | * "model.pbtxt": human-readable definition of the graph. Not |
| | needed for deployment. |
| | |
| | Args: |
| | output_dir (str): the output directory to save protobuf files. |
| | """ |
| | logger = logging.getLogger(__name__) |
| | logger.info("Saving model to {} ...".format(output_dir)) |
| | if not PathManager.exists(output_dir): |
| | PathManager.mkdirs(output_dir) |
| |
|
| | with PathManager.open(os.path.join(output_dir, "model.pb"), "wb") as f: |
| | f.write(self._predict_net.SerializeToString()) |
| | with PathManager.open(os.path.join(output_dir, "model.pbtxt"), "w") as f: |
| | f.write(str(self._predict_net)) |
| | with PathManager.open(os.path.join(output_dir, "model_init.pb"), "wb") as f: |
| | f.write(self._init_net.SerializeToString()) |
| |
|
| | def save_graph(self, output_file, inputs=None): |
| | """ |
| | Save the graph as SVG format. |
| | |
| | Args: |
| | output_file (str): a SVG file |
| | inputs: optional inputs given to the model. |
| | If given, the inputs will be used to run the graph to record |
| | shape of every tensor. The shape information will be |
| | saved together with the graph. |
| | """ |
| | from .caffe2_export import run_and_save_graph |
| |
|
| | if inputs is None: |
| | save_graph(self._predict_net, output_file, op_only=False) |
| | else: |
| | size_divisibility = get_pb_arg_vali(self._predict_net, "size_divisibility", 0) |
| | device = get_pb_arg_vals(self._predict_net, "device", b"cpu").decode("ascii") |
| | inputs = convert_batched_inputs_to_c2_format(inputs, size_divisibility, device) |
| | inputs = [x.cpu().numpy() for x in inputs] |
| | run_and_save_graph(self._predict_net, self._init_net, inputs, output_file) |
| |
|
| | @staticmethod |
| | def load_protobuf(dir): |
| | """ |
| | Args: |
| | dir (str): a directory used to save Caffe2Model with |
| | :meth:`save_protobuf`. |
| | The files "model.pb" and "model_init.pb" are needed. |
| | |
| | Returns: |
| | Caffe2Model: the caffe2 model loaded from this directory. |
| | """ |
| | predict_net = caffe2_pb2.NetDef() |
| | with PathManager.open(os.path.join(dir, "model.pb"), "rb") as f: |
| | predict_net.ParseFromString(f.read()) |
| |
|
| | init_net = caffe2_pb2.NetDef() |
| | with PathManager.open(os.path.join(dir, "model_init.pb"), "rb") as f: |
| | init_net.ParseFromString(f.read()) |
| |
|
| | return Caffe2Model(predict_net, init_net) |
| |
|
| | def __call__(self, inputs): |
| | """ |
| | An interface that wraps around a Caffe2 model and mimics detectron2's models' |
| | input/output format. See details about the format at :doc:`/tutorials/models`. |
| | This is used to compare the outputs of caffe2 model with its original torch model. |
| | |
| | Due to the extra conversion between Pytorch/Caffe2, this method is not meant for |
| | benchmark. Because of the conversion, this method also has dependency |
| | on detectron2 in order to convert to detectron2's output format. |
| | """ |
| | if self._predictor is None: |
| | self._predictor = ProtobufDetectionModel(self._predict_net, self._init_net) |
| | return self._predictor(inputs) |
| |
|