Source code for schrodinger.tasks.hosts

import copy

import schrodinger.job.jobcontrol as jobcontrol  # for get_hosts()
from schrodinger.infra import gpgpu_utils
from schrodinger.models.json import JsonableClassMixin

LOCALHOST = 'localhost'
LOCALHOST_GPU = 'localhost-gpu'


[docs]def strip_gpu_from_localhost(hostname): """ Check host strings to see if 'localhost-gpu' was specified and, if so, replace with 'localhost' :param hostname: Host name to be checked :type hostname: str :return: The actual hostname value to be used in a job command. :rtype: str """ if hostname == LOCALHOST_GPU: return LOCALHOST return hostname
[docs]def get_GPGPUs(hostname): gpulist = [] if hostname == LOCALHOST: return [] if hostname == LOCALHOST_GPU: gpgpulist = gpgpu_utils.get_local_gpgpus() else: gpgpulist = gpgpu_utils.get_remote_gpgpus(hostname) for gpgpu in gpgpulist: gpulist.append(Gpu(gpgpu[0], gpgpu[1])) return gpulist
[docs]class Host(JsonableClassMixin, jobcontrol.Host): """ Extension of jobcontrol.Host class with GPUs. """ CPUTYPE, GPUTYPE = ('cpu', 'gpu')
[docs] def __init__(self, name, processors=0, num_gpus=0, gpulist=None): jobcontrol.Host.__init__(self, name) self.processors = processors self.num_gpus = num_gpus self.gpu_list = gpulist self.ncpus = True # Whether to display number of cpus in label
[docs] def toJsonImplementation(self): return { 'name': self.name, 'processors': self.processors, 'num_gpus': self.num_gpus, 'gpulist': self.gpu_list, }
[docs] @classmethod def fromJsonImplementation(cls, json_dict): gpulist = json_dict.pop('gpulist') if gpulist is not None: gpulist = [Gpu.fromJsonImplementation(data) for data in gpulist] return cls(gpulist=gpulist, **json_dict)
[docs] @classmethod def fromJobControlHost(cls, jchost, ncpus): host = copy.copy(jchost) host.__class__ = Host host.ncpus = ncpus host.autoSetGpuList() return host
[docs] def setGpuList(self, gpulist): self.gpu_list = gpulist self.num_gpus = len(gpulist)
[docs] def autoSetGpuList(self): gpulist = get_GPGPUs(self.name) self.setGpuList(gpulist)
[docs] def hostType(self): """ Used to determine what type of host this is. """ if self.num_gpus > 0: return self.GPUTYPE return self.CPUTYPE
[docs] def label(self): """ Returns the string to show in controls, etc. """ if not self.ncpus: return self.name if self.hostType() == self.GPUTYPE: return '%s (%d, %d)' % (self.name, self.processors, self.num_gpus) return '%s (%d)' % (self.name, self.processors)
[docs] def units(self): """ Return the unit string for the type of processors provided by this host. """ if self.hostType() == self.CPUTYPE: return 'processors' else: return 'GPUs'
[docs] def maxNum(self): """ Returns the number of processors for the type of host - for GPU host, return the number of GPUs, for non-GPU hosts, return the number of CPUs. """ if self.hostType() == self.CPUTYPE: return self.processors else: return self.num_gpus
def __str__(self): return self.label() def __repr__(self): return self.label() def __eq__(self, other): """ Allows equality comparisons between two different host objects that represent the same host. This is necessary if get_hosts() is called two different times and hosts from one call are to be compared with hosts from the other call. Hosts are considered equal if they have the same name and processors. :param other: the host to be compared with self :type other: Host """ if not isinstance(other, Host): return return self.label() == other.label() def __hash__(self): return hash(self.label())
[docs]class Gpu(JsonableClassMixin):
[docs] def __init__(self, index, desc): self.index = index self.description = desc
[docs] def toJsonImplementation(self): return { 'index': self.index, 'desc': self.description, }
[docs] @classmethod def fromJsonImplementation(cls, json_dict): return cls(**json_dict)
[docs]def get_hosts(ncpus=True, excludeGPGPUs=True): """ Return a list of Host objects for use in config dialogs. Note these are a subclass of jobcontrol.Host which has additional features for text labels and accounting for GPUs. If schrodinger.hosts file is missing, only localhost will be returned. If it is unreadable, then jobcontrol.UnreadableHostsFileException will be raised. :param ncpus: whether host text labels should include number of processors :type ncpus: bool :param excludeGPGPUs: whether to exclude GPU hosts from the list :type excludeGPGPUs: bool :return: a list of Host objects :rtype: list :raises jobcontrol.UnreadableHostsFileException: If host file cannot be read """ hosts = [] try: host_object_list = jobcontrol.get_hosts() except jobcontrol.MissingHostsFileException: print('WARNING: File at path $SCHRODINGER_HOSTS does not exist') # If hosts file is not found, return just the localhost PANEL-3653 # because jobs to localhosts can still be submitted. host_object_list = [jobcontrol.Host('localhost')] _insert_local_gpu_host(host_object_list) for idx, oldhost in enumerate(host_object_list): host = Host.fromJobControlHost(oldhost, ncpus) if excludeGPGPUs and host.gpu_list: continue hosts.append(host) return hosts
[docs]def get_host_by_name(name): """ Get the Host object from the hosts list that matches the specified hostname. """ if name == LOCALHOST: # get cached host for localhost for performance reasons try: _host = jobcontrol.get_host(name) except jobcontrol.MissingHostsFileException: _host = jobcontrol.Host('localhost') return Host.fromJobControlHost(_host, True) host_list = get_hosts(excludeGPGPUs=False) for host in host_list: if host.name == name: return host raise RuntimeError(f'Could not find a host named {name}.')
def _insert_local_gpu_host(host_object_list): """ Given a list of host objects, create a local GPU host if appropriate and insert it in the list. :param host_object_list: List of host objectts :type host_object_list: list(Host) """ local_gpu_host_idx = None local_gpu_host = None for i, host in enumerate(host_object_list): if host.name == LOCALHOST: if get_GPGPUs(LOCALHOST_GPU): local_gpu_host_idx = i + 1 local_gpu_host = copy.copy(host) break if local_gpu_host_idx is not None: local_gpu_host.name = LOCALHOST_GPU host_object_list.insert(local_gpu_host_idx, local_gpu_host)