This repository was archived by the owner on Jan 7, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Initial support for Torch7 in DIGITS #324
Merged
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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)' | ||
|
|
||
| # 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) | ||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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
TorchFrameworkclass 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?There was a problem hiding this comment.
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_idfield 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?There was a problem hiding this comment.
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.