Skip to content
This repository was archived by the owner on Jan 7, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ install:
- pushd .
- ./scripts/travis/install-caffe.sh $(pwd)/deps/caffe

# Torch
- ./scripts/travis/install-torch.sh $(pwd)/deps/torch

# DIGITS
- sudo apt-get install graphviz
# conda (fast)
Expand All @@ -29,6 +32,7 @@ install:
- pip install -r requirements_test.txt
before_script:
- export CAFFE_HOME=$(pwd)/deps/caffe
- source $(pwd)/deps/torch/install/bin/torch-activate
script:
./digits-test -v --with-coverage --cover-package=digits,tools,scripts
after_success:
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ For greater performance, you can also install cuDNN.

At least one deep learning framework backend is required.

* Caffe (NVIDIA's fork) - [installation instructions](docs/InstallCaffe.md)
* [Mandatory] Caffe (NVIDIA's fork) - [installation instructions](docs/InstallCaffe.md)
* [Optional] Torch7 - [installation instructions](docs/InstallTorch.md)

## Install DIGITS

Expand Down
2 changes: 2 additions & 0 deletions digits/config/current_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from server_name import ServerNameOption
from secret_key import SecretKeyOption
from caffe_option import CaffeOption
from torch_option import TorchOption

option_list = None

Expand All @@ -24,6 +25,7 @@ def reset():
ServerNameOption(),
SecretKeyOption(),
CaffeOption(),
TorchOption(),
]

reset()
Expand Down
110 changes: 110 additions & 0 deletions digits/config/torch_option.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# Copyright (c) 2015, NVIDIA CORPORATION. All rights reserved.

import os
import re
import sys
import imp
import platform
import subprocess

from digits import device_query
import config_option
import prompt

class TorchOption(config_option.FrameworkOption):
@staticmethod
def config_file_key():
return 'torch_root'

@classmethod
def prompt_title(cls):
return 'Torch'

@classmethod
def prompt_message(cls):
return 'Where is torch installed?'

def optional(self):
return True

def suggestions(self):
suggestions = []
if 'TORCH_ROOT' in os.environ:
d = os.environ['TORCH_ROOT']
try:
suggestions.append(prompt.Suggestion(
self.validate(d), 'R',
desc='TORCH_ROOT', default=True))
except config_option.BadValue as e:
print 'TORCH_ROOT "%s" is invalid:' % d
print '\t%s' % e
if 'TORCH_HOME' in os.environ:
d = os.environ['TORCH_HOME']
try:
default = True
if len(suggestions) > 0:
default = False
suggestions.append(prompt.Suggestion(
self.validate(d), 'H',
desc='TORCH_HOME', default=default))
except config_option.BadValue as e:
print 'TORCH_HOME "%s" is invalid:' % d
print '\t%s' % e
suggestions.append(prompt.Suggestion('<PATHS>', 'P',
desc='PATH/TORCHPATH', default=True))
return suggestions

@staticmethod
def is_path():
return True

@classmethod
def validate(cls, value):
if not value:
return value

if value == '<PATHS>':
# Find the executable
executable = cls.find_executable('th')
if not executable:
raise config_option.BadValue('torch binary not found in PATH')
#cls.validate_version(executable)
return value
else:
# Find the executable
value = os.path.abspath(value)
if not os.path.isdir(value):
raise config_option.BadValue('"%s" is not a directory' % value)
expected_path = os.path.join(value, 'bin', 'th')
if not os.path.exists(expected_path):
raise config_option.BadValue('torch binary not found at "%s"' % value)
#cls.validate_version(expected_path)
return value

@staticmethod
def find_executable(program):
"""
Finds an executable by searching through PATH
Returns the path to the executable or None
"""
for path in os.environ['PATH'].split(os.pathsep):
path = path.strip('"')
executable = os.path.join(path, program)
if os.path.isfile(executable) and os.access(executable, os.X_OK):
return executable
return None

@classmethod
def validate_version(cls, executable):
"""
Utility for checking the caffe version from within validate()
Throws BadValue

Arguments:
executable -- path to a caffe executable
"""
# Currently DIGITS don't have any restrictions on Torch version, so no need to implement this.
pass

def apply(self):
pass
2 changes: 1 addition & 1 deletion digits/dataset/tasks/analyze_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ def offer_resources(self, resources):
return None

@override
def task_arguments(self, resources):
def task_arguments(self, resources, env):
args = [sys.executable, os.path.join(
os.path.dirname(os.path.dirname(os.path.abspath(digits.__file__))),
'tools', 'analyze_db.py'),
Expand Down
2 changes: 1 addition & 1 deletion digits/dataset/tasks/create_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ def offer_resources(self, resources):
return None

@override
def task_arguments(self, resources):
def task_arguments(self, resources, env):
args = [sys.executable, os.path.join(
os.path.dirname(os.path.dirname(os.path.abspath(digits.__file__))),
'tools', 'create_db.py'),
Expand Down
2 changes: 1 addition & 1 deletion digits/dataset/tasks/parse_folder.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ def offer_resources(self, resources):
return None

@override
def task_arguments(self, resources):
def task_arguments(self, resources, env):
args = [sys.executable, os.path.join(
os.path.dirname(os.path.dirname(os.path.abspath(digits.__file__))),
'tools', 'parse_folder.py'),
Expand Down
6 changes: 6 additions & 0 deletions digits/frameworks/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@

from framework import Framework
from caffe_framework import CaffeFramework
from torch_framework import TorchFramework
from digits.config import config_value

#
# create framework instances
#

# torch is optional
torch = TorchFramework() if config_value('torch_root') else None

# caffe is mandatory
caffe = CaffeFramework()

Expand All @@ -21,6 +25,8 @@ def get_frameworks():
there may be more than one instance per framework class
"""
frameworks = [caffe]
if torch:
frameworks.append(torch)
return frameworks

def get_framework_by_id(framework_id):
Expand Down
1 change: 1 addition & 0 deletions digits/frameworks/caffe_framework.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ def get_network_from_previous(self, previous_network):
ip_layers[-1].name = '%s_retrain' % ip_layers[-1].name
return network

@override
def get_network_visualization(self, desc):
"""
return visualization of network
Expand Down
11 changes: 11 additions & 0 deletions digits/frameworks/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,17 @@ def __init__(self, message):
def __str__(self):
return repr(self.message)

@subclass
class NetworkVisualizationError(Error):
"""
Errors that occur when validating a network
"""
def __init__(self, message):
self.message = message

def __str__(self):
return repr(self.message)

@subclass
class InferenceError(Error):
"""
Expand Down
162 changes: 162 additions & 0 deletions digits/frameworks/torch_framework.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
# Copyright (c) 2015, NVIDIA CORPORATION. All rights reserved.

import os
import digits
import re
import subprocess
import time
import tempfile
import flask

from framework import Framework
from digits import utils
from digits.config import config_value
from digits.model.tasks import TorchTrainTask
from digits.utils import subclass, override
from errors import Error, NetworkVisualizationError, BadNetworkError

@subclass
class TorchFramework(Framework):

"""
Defines required methods to interact with the Torch framework
"""

# short descriptive name
NAME = 'Torch (experimental)'

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One bothersome thing about putting the "experimental" text in the TorchFramework class is that old torch jobs will continue to be flagged as "experimental" even when DIGITS upgrades to "full support" (whatever that means). Is there a way to put the "experimental" flagging in the template somewhere rather than here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The 'descriptive' name is only used in the navigation pane there on the model creation pane. The model job keeps a link to the framework through the train task and its framework_id field there - the framework ID is what's shown on the home page and in the previous networks tab. We can change the descriptive name and that will change the display on the navigation pane however the framework ID will still be 'torch'. When we want to support Torch9 we can create a new framework ID to denote the change in major version. Is that OK?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I see. This isn't a TrainTask, so it doesn't get pickled. Nevermind.


# identifier of framework class
CLASS = 'torch'

# whether this framework can shuffle data during training
CAN_SHUFFLE_DATA = True

def __init__(self):
super(TorchFramework, self).__init__()
# id must be unique
self.framework_id = self.CLASS

@override
def create_train_task(self, **kwargs):
"""
create train task
"""
return TorchTrainTask(framework_id = self.framework_id, **kwargs)

@override
def get_standard_network_desc(self, network):
"""
return description of standard network
"""
networks_dir = os.path.join(os.path.dirname(digits.__file__), 'standard-networks', self.CLASS)

# Torch's GoogLeNet and AlexNet models are placed in sub folder
if (network == "alexnet" or network == "googlenet"):
networks_dir = os.path.join(networks_dir, 'ImageNet-Training')

for filename in os.listdir(networks_dir):
path = os.path.join(networks_dir, filename)
if os.path.isfile(path):
match = None
match = re.match(r'%s.lua' % network, filename)
if match:
with open(path) as infile:
return infile.read()
# return None if not found
return None

@override
def get_network_from_desc(self, network_desc):
"""
return network object from a string representation
"""
# return the same string
return network_desc

@override
def get_network_from_previous(self, previous_network):
"""
return new instance of network from previous network
"""
# return the same string
return previous_network

@override
def validate_network(self, data):
"""
validate a network
"""
return True

@override
def get_network_visualization(self, desc):
"""
return visualization of network
"""
# save network description to temporary file
_, temp_network_path = tempfile.mkstemp(suffix='.lua')
with open(temp_network_path, "w") as outfile:
outfile.write(desc)

try: # do this in a try..finally clause to make sure we delete the temp file
# build command line
if config_value('torch_root') == '<PATHS>':
torch_bin = 'th'
else:
torch_bin = os.path.join(config_value('torch_root'), 'bin', 'th')

args = [torch_bin,
os.path.join(os.path.dirname(os.path.dirname(digits.__file__)),'tools','torch','main.lua'),
'--network=%s' % os.path.splitext(os.path.basename(temp_network_path))[0],
'--networkDirectory=%s' % os.path.dirname(temp_network_path),
'--visualizeModel=yes'
]

# execute command
p = subprocess.Popen(args,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
close_fds=True,
)

regex = re.compile('\x1b\[[0-9;]*m', re.UNICODE) #TODO: need to include regular expression for MAC color codes

# the network description will be accumulated from the command output
# when collecting_net_definition==True
collecting_net_definition = False
desc = []
unrecognized_output = []
while p.poll() is None:
for line in utils.nonblocking_readlines(p.stdout):
if line is not None:
# Remove whitespace and color codes. color codes are appended to begining and end of line by torch binary i.e., 'th'. Check the below link for more information
# https://groups.google.com/forum/#!searchin/torch7/color$20codes/torch7/8O_0lSgSzuA/Ih6wYg9fgcwJ
line = regex.sub('', line)
timestamp, level, message = TorchTrainTask.preprocess_output_torch(line.strip())
if message:
if message.startswith('Network definition'):
collecting_net_definition = not collecting_net_definition
else:
if collecting_net_definition:
desc.append(line)
elif len(line):
unrecognized_output.append(line)
else:
time.sleep(0.05)

if not len(desc):
# we did not find a network description
raise NetworkVisualizationError(''.join(unrecognized_output))
else:
output = flask.Markup('<pre>')
for line in desc:
output += flask.Markup.escape(line)
output += flask.Markup('</pre>')
return output
finally:
os.remove(temp_network_path)





Loading