Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make detectron2 usable without building C extensions #3909

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
14 changes: 14 additions & 0 deletions .circleci/config.yml
Expand Up @@ -130,6 +130,7 @@ install_detectron2: &install_detectron2
command: |
# Remove first, in case it's in the CI cache
pip uninstall -y detectron2

pip install --progress-bar off -e .[all]
python -m detectron2.utils.collect_env
./datasets/prepare_for_tests.sh
Expand All @@ -140,6 +141,17 @@ run_unittests: &run_unittests
command: |
pytest -v --durations=15 tests # parallel causes some random failures

uninstall_tests: &uninstall_tests
- run:
name: Run Tests After Uninstalling
command: |
pip uninstall -y detectron2
# Remove built binaries
rm -rf build/ detectron2/*.so
# Tests that code is importable without installation
PYTHONPATH=. ./.circleci/import-tests.sh


# -------------------------------------------------------------------------------------
# Jobs to run
# -------------------------------------------------------------------------------------
Expand All @@ -163,6 +175,7 @@ jobs:
- <<: *install_linux_dep
- <<: *install_detectron2
- <<: *run_unittests
- <<: *uninstall_tests

- save_cache:
paths:
Expand All @@ -188,6 +201,7 @@ jobs:
- <<: *install_linux_dep
- <<: *install_detectron2
- <<: *run_unittests
- <<: *uninstall_tests

- save_cache:
paths:
Expand Down
16 changes: 16 additions & 0 deletions .circleci/import-tests.sh
@@ -0,0 +1,16 @@
#!/bin/bash -e
# Copyright (c) Facebook, Inc. and its affiliates.

# Test that import works without building detectron2.

# Check that _C is not importable
python -c "from detectron2 import _C" > /dev/null 2>&1 && {
echo "This test should be run without building detectron2."
exit 1
}

# Check that other modules are still importable, even when _C is not importable
python -c "from detectron2 import modeling"
python -c "from detectron2 import modeling, data"
python -c "from detectron2 import evaluation, export, checkpoint"
python -c "from detectron2 import utils, engine"
2 changes: 1 addition & 1 deletion .flake8
Expand Up @@ -2,7 +2,7 @@
# Keep in sync with setup.cfg which is used for source packages.

[flake8]
ignore = W503, E203, E221, C901, C408, E741, C407, B017
ignore = W503, E203, E221, C901, C408, E741, C407, B017, F811
max-line-length = 100
max-complexity = 18
select = B,C,E,F,W,T4,B9
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/workflow.yml
Expand Up @@ -51,17 +51,17 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set up Python 3.6
- name: Set up Python 3.7
uses: actions/setup-python@v2
with:
python-version: 3.6
python-version: 3.7
- name: Cache dependencies
uses: actions/cache@v2
with:
path: |
${{ env.pythonLocation }}/lib/python3.6/site-packages
${{ env.pythonLocation }}/lib/python3.7/site-packages
~/.torch
key: ${{ runner.os }}-torch${{ matrix.torch }}-${{ hashFiles('setup.py') }}-20210420
key: ${{ runner.os }}-torch${{ matrix.torch }}-${{ hashFiles('setup.py') }}-20220119

- name: Install dependencies
run: |
Expand Down
9 changes: 8 additions & 1 deletion detectron2/evaluation/fast_eval_api.py
Expand Up @@ -5,7 +5,7 @@
import time
from pycocotools.cocoeval import COCOeval

from detectron2 import _C
from detectron2.utils.develop import create_dummy_class

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -119,3 +119,10 @@ def accumulate(self):
self.eval["scores"] = np.array(self.eval["scores"]).reshape(self.eval["counts"])
toc = time.time()
logger.info("COCOeval_opt.accumulate() finished in {:0.2f} seconds.".format(toc - tic))


try:
from detectron2 import _C
except ImportError:
_msg = "detectron2 is not compiled successfully, please build following the instructions!"
COCOeval_opt = create_dummy_class("COCOeval_opt", "detectron2._C", _msg)
ppwwyyxx marked this conversation as resolved.
Show resolved Hide resolved
15 changes: 14 additions & 1 deletion detectron2/layers/deform_conv.py
Expand Up @@ -8,7 +8,7 @@
from torch.nn.modules.utils import _pair
from torchvision.ops import deform_conv2d

from detectron2 import _C
from detectron2.utils.develop import create_dummy_class, create_dummy_func

from .wrappers import _NewEmptyTensorOp

Expand Down Expand Up @@ -47,6 +47,7 @@ def forward(
ctx.bufs_ = [input.new_empty(0), input.new_empty(0)] # columns, ones

if not input.is_cuda:
# TODO: let torchvision support full features of our deformconv.
if deformable_groups != 1:
raise NotImplementedError(
"Deformable Conv with deformable_groups != 1 is not supported on CPUs!"
Expand Down Expand Up @@ -499,3 +500,15 @@ def extra_repr(self):
tmpstr += ", deformable_groups=" + str(self.deformable_groups)
tmpstr += ", bias=" + str(self.with_bias)
return tmpstr


try:
from detectron2 import _C
except ImportError:
# TODO: register ops natively so there is no need to import _C.
_msg = "detectron2 is not compiled successfully, please build following the instructions!"
_args = ("detectron2._C", _msg)
DeformConv = create_dummy_class("DeformConv", *_args)
ModulatedDeformConv = create_dummy_class("ModulatedDeformConv", *_args)
deform_conv = create_dummy_func("deform_conv", *_args)
modulated_deform_conv = create_dummy_func("modulated_deform_conv", *_args)
59 changes: 59 additions & 0 deletions detectron2/utils/develop.py
@@ -0,0 +1,59 @@
# Copyright (c) Facebook, Inc. and its affiliates.
""" Utilities for developers only.
These are not visible to users (not automatically imported). And should not
appeared in docs."""
# adapted from https://github.com/tensorpack/tensorpack/blob/master/tensorpack/utils/develop.py


def create_dummy_class(klass, dependency, message=""):
"""
When a dependency of a class is not available, create a dummy class which throws ImportError
when used.

Args:
klass (str): name of the class.
dependency (str): name of the dependency.
message: extra message to print
Returns:
class: a class object
"""
err = "Cannot import '{}', therefore '{}' is not available.".format(dependency, klass)
if message:
err = err + " " + message

class _DummyMetaClass(type):
# throw error on class attribute access
def __getattr__(_, __):
raise ImportError(err)

class _Dummy(object, metaclass=_DummyMetaClass):
# throw error on constructor
def __init__(self, *args, **kwargs):
raise ImportError(err)

return _Dummy


def create_dummy_func(func, dependency, message=""):
"""
When a dependency of a function is not available, create a dummy function which throws
ImportError when used.

Args:
func (str): name of the function.
dependency (str or list[str]): name(s) of the dependency.
message: extra message to print
Returns:
function: a function object
"""
err = "Cannot import '{}', therefore '{}' is not available.".format(dependency, func)
if message:
err = err + " " + message

if isinstance(dependency, (list, tuple)):
dependency = ",".join(dependency)

def _dummy(*args, **kwargs):
raise ImportError(err)

return _dummy
1 change: 0 additions & 1 deletion tools/train_net.py
Expand Up @@ -19,7 +19,6 @@
import logging
import os
from collections import OrderedDict
import torch

import detectron2.utils.comm as comm
from detectron2.checkpoint import DetectionCheckpointer
Expand Down