diff --git a/Dockerfile b/Dockerfile index 1fc9986b08..f5f5992eca 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,128 @@ -# Use an official Python runtime as a parent image -FROM python:3.6-slim +# Based off of python:3.6-slim, except that we are using ubuntu instead of debian. +FROM ubuntu:16.04 + + +# ensure local python is preferred over distribution python +ENV PATH /usr/local/bin:$PATH + +# http://bugs.python.org/issue19846 +# > At the moment, setting "LANG=C" on a Linux system *fundamentally breaks Python 3*, and that's not OK. +ENV LANG C.UTF-8 + +# runtime dependencies +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates \ + libexpat1 \ + libffi6 \ + libgdbm3 \ + libreadline6 \ + libsqlite3-0 \ + libssl1.0.0 \ + && rm -rf /var/lib/apt/lists/* + +ENV GPG_KEY 0D96DF4D4110E5C43FBFB17F2D347EA6AA65421D +ENV PYTHON_VERSION 3.6.4 + +RUN set -ex \ + && buildDeps=" \ + dpkg-dev \ + gcc \ + libbz2-dev \ + libc6-dev \ + libexpat1-dev \ + libffi-dev \ + libgdbm-dev \ + liblzma-dev \ + libncursesw5-dev \ + libreadline-dev \ + libsqlite3-dev \ + libssl-dev \ + make \ + tcl-dev \ + tk-dev \ + wget \ + xz-utils \ + zlib1g-dev \ +# as of Stretch, "gpg" is no longer included by default + $(command -v gpg > /dev/null || echo 'gnupg dirmngr') \ + " \ + && apt-get update && apt-get install -y $buildDeps --no-install-recommends && rm -rf /var/lib/apt/lists/* \ + \ + && wget -O python.tar.xz "https://www.python.org/ftp/python/${PYTHON_VERSION%%[a-z]*}/Python-$PYTHON_VERSION.tar.xz" \ + && wget -O python.tar.xz.asc "https://www.python.org/ftp/python/${PYTHON_VERSION%%[a-z]*}/Python-$PYTHON_VERSION.tar.xz.asc" \ + && export GNUPGHOME="$(mktemp -d)" \ + && gpg --keyserver ha.pool.sks-keyservers.net --recv-keys "$GPG_KEY" \ + && gpg --batch --verify python.tar.xz.asc python.tar.xz \ + && rm -rf "$GNUPGHOME" python.tar.xz.asc \ + && mkdir -p /usr/src/python \ + && tar -xJC /usr/src/python --strip-components=1 -f python.tar.xz \ + && rm python.tar.xz \ + \ + && cd /usr/src/python \ + && gnuArch="$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)" \ + && ./configure \ + --build="$gnuArch" \ + --enable-loadable-sqlite-extensions \ + --enable-shared \ + --with-system-expat \ + --with-system-ffi \ + --without-ensurepip \ + && make -j "$(nproc)" \ + && make install \ + && ldconfig \ + \ + && apt-get purge -y --auto-remove $buildDeps \ + \ + && find /usr/local -depth \ + \( \ + \( -type d -a \( -name test -o -name tests \) \) \ + -o \ + \( -type f -a \( -name '*.pyc' -o -name '*.pyo' \) \) \ + \) -exec rm -rf '{}' + \ + && rm -rf /usr/src/python + +# make some useful symlinks that are expected to exist +RUN cd /usr/local/bin \ + && ln -s idle3 idle \ + && ln -s pydoc3 pydoc \ + && ln -s python3 python \ + && ln -s python3-config python-config + +# if this is called "PIP_VERSION", pip explodes with "ValueError: invalid truth value ''" +ENV PYTHON_PIP_VERSION 9.0.3 + +RUN set -ex; \ + \ + apt-get update; \ + apt-get install -y --no-install-recommends wget; \ + rm -rf /var/lib/apt/lists/*; \ + \ + wget -O get-pip.py 'https://bootstrap.pypa.io/get-pip.py'; \ + \ + apt-get purge -y --auto-remove wget; \ + \ + python get-pip.py \ + --disable-pip-version-check \ + --no-cache-dir \ + "pip==$PYTHON_PIP_VERSION" \ + ; \ + pip --version; \ + \ + find /usr/local -depth \ + \( \ + \( -type d -a \( -name test -o -name tests \) \) \ + -o \ + \( -type f -a \( -name '*.pyc' -o -name '*.pyo' \) \) \ + \) -exec rm -rf '{}' +; \ + rm -f get-pip.py + RUN apt-get update && apt-get -y upgrade +# xvfb is used to do CPU based rendering of Unity +RUN apt-get install -y xvfb + + ADD python/requirements.txt . RUN pip install --trusted-host pypi.python.org -r requirements.txt diff --git a/README.md b/README.md index b11a99a0f5..171ce6c06f 100755 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ to the wider research and game developer communities. * Built-in support for Imitation Learning * Flexible Agent control with On Demand Decision Making * Visualizing network outputs within the environment -* Simplified set-up with Docker (Experimental) +* Simplified set-up with Docker ## Documentation and References diff --git a/docs/Installation.md b/docs/Installation.md index 46380ec495..0530ffd478 100644 --- a/docs/Installation.md +++ b/docs/Installation.md @@ -2,7 +2,7 @@ To install and use ML-Agents, you need install Unity, clone this repository and install Python with additional dependencies. Each of the subsections -below overviews each step, in addition to an experimental Docker set-up. +below overviews each step, in addition to a Docker set-up. ## Install **Unity 2017.1** or Later @@ -49,7 +49,7 @@ and run from the command line: pip3 install . -## Docker-based Installation (Experimental) +## Docker-based Installation If you'd like to use Docker for ML-Agents, please follow [this guide](Using-Docker.md). diff --git a/docs/ML-Agents-Overview.md b/docs/ML-Agents-Overview.md index 59e3317391..f353926505 100644 --- a/docs/ML-Agents-Overview.md +++ b/docs/ML-Agents-Overview.md @@ -428,8 +428,7 @@ the broadcasting feature * **Docker Set-up (Experimental)** - To facilitate setting up ML-Agents without installing Python or TensorFlow directly, we provide a [guide](Using-Docker.md) on how -to create and run a Docker container. Due to limitations on rendering visual -observations, this feature is marked experimental. +to create and run a Docker container. * **Cloud Training on AWS** - To facilitate using ML-Agents on Amazon Web Services (AWS) machines, we provide a diff --git a/docs/Readme.md b/docs/Readme.md index f967685b06..2f25361ffd 100644 --- a/docs/Readme.md +++ b/docs/Readme.md @@ -7,7 +7,7 @@ * [Background: TensorFlow](Background-TensorFlow.md) * [Installation & Set-up](Installation.md) * [Background: Jupyter Notebooks](Background-Jupyter.md) - * [Docker Set-up (Experimental)](Using-Docker.md) + * [Docker Set-up](Using-Docker.md) * [Getting Started with the 3D Balance Ball Environment](Getting-Started-with-Balance-Ball.md) * [Example Environments](Learning-Environment-Examples.md) diff --git a/docs/Using-Docker.md b/docs/Using-Docker.md index 8f8f826d21..4d8e5bf585 100644 --- a/docs/Using-Docker.md +++ b/docs/Using-Docker.md @@ -1,6 +1,7 @@ -# Using Docker For ML-Agents (Experimental) +# Using Docker For ML-Agents + +We currently offer a solution for Windows and Mac users who would like to do training or inference using Docker. This option may be appealing to those who would like to avoid installing Python and TensorFlow themselves. The current setup forces both TensorFlow and Unity to _only_ rely on the CPU for computations. Consequently, our Docker simulation does not use a GPU and uses [`Xvfb`](https://en.wikipedia.org/wiki/Xvfb) to do visual rendering. `Xvfb` is a utility that enables `ML-Agents` (or any other application) to do rendering virtually i.e. it does not assume that the machine running `ML-Agents` has a GPU or a display attached to it. This means that rich environments which involve agents using camera-based visual observations might be slower. -We currently offer an experimental solution for Windows and Mac users who would like to do training or inference using Docker. This option may be appealing to those who would like to avoid installing Python and TensorFlow themselves. The current setup forces both TensorFlow and Unity to _only_ rely on the CPU for computations. Consequently, our Docker support is limited to environments whose agents **do not** use camera-based visual observations. For example, the [GridWorld](Learning-Environment-Examples.md#gridworld) environment is **not** supported. ## Requirements - Unity _Linux Build Support_ Component @@ -26,13 +27,12 @@ Since Docker typically runs a container sharing a (linux) kernel with the host m Unity environment **has** to be built for the **linux platform**. When building a Unity environment, please select the following options from the the Build Settings window: - Set the _Target Platform_ to `Linux` - Set the _Architecture_ to `x86_64` -- `Uncheck` the _Development Build_ option -- `Check` the _Headless Mode_ option. (_This is required because the Unity binary will run in a container that does not have graphics drivers installed_.) - -![Build Settings For Docker](images/docker_build_settings.png) +- If the environment does not contain visual observations, you can select the `headless` option here. Then click `Build`, pick an environment name (e.g. `3DBall`) and set the output directory to `unity-volume`. After building, ensure that the file `.x86_64` and subdirectory `_Data/` are created under `unity-volume`. +![Build Settings For Docker](images/docker_build_settings.png) + ### Build the Docker Container First, make sure the Docker engine is running on your machine. Then build the Docker container by calling the following command at the top-level of the repository: @@ -66,7 +66,7 @@ For the `3DBall` environment, for example this would be: ``` docker run --name 3DBallContainer.first.trial \ --mount type=bind,source="$(pwd)"/unity-volume,target=/unity-volume \ - balance.ball.v0.1:latest 3Dball \ + balance.ball.v0.1:latest 3DBall \ --docker-target-name=unity-volume \ --train \ --run-id=3dball_first_trial @@ -77,7 +77,7 @@ For more detail on Docker mounts, check out [these](https://docs.docker.com/stor ### Stopping Container and Saving State -If you are satisfied with the training progress, you can stop the Docker container while saving state using the following command: +If you are satisfied with the training progress, you can stop the Docker container while saving state by either using `Ctrl+C` or `⌘+C` (Mac) or by using the following command: ``` docker kill --signal=SIGINT diff --git a/docs/images/docker_build_settings.png b/docs/images/docker_build_settings.png index 29c7c4cd44..e0325a34b6 100644 Binary files a/docs/images/docker_build_settings.png and b/docs/images/docker_build_settings.png differ diff --git a/python/unityagents/environment.py b/python/unityagents/environment.py index b12f735367..9a1dc2e3b7 100755 --- a/python/unityagents/environment.py +++ b/python/unityagents/environment.py @@ -23,7 +23,7 @@ class UnityEnvironment(object): def __init__(self, file_name, worker_id=0, base_port=5005, curriculum=None, - seed=0): + seed=0, docker_training=False): """ Starts a new unity environment and establishes a connection with the environment. Notice: Currently communication between Unity and Python takes place over an open socket without authentication. @@ -32,6 +32,7 @@ def __init__(self, file_name, worker_id=0, :string file_name: Name of Unity environment binary. :int base_port: Baseline port number to connect to Unity environment over. worker_id increments over this. :int worker_id: Number to add to communication port (5005) [0]. Used for asynchronous agent scenarios. + :param docker_training: Informs this class whether the process is being run within a container. """ atexit.register(self.close) @@ -95,11 +96,38 @@ def __init__(self, file_name, worker_id=0, else: logger.debug("This is the launch string {}".format(launch_string)) # Launch Unity environment - proc1 = subprocess.Popen( - [launch_string, - '--port', str(self.port), - '--seed', str(seed)]) - + if docker_training == False: + proc1 = subprocess.Popen( + [launch_string, + '--port', str(self.port), + '--seed', str(seed)]) + else: + """ + Comments for future maintenance: + xvfb-run is a wrapper around Xvfb, a virtual xserver where all + rendering is done to virtual memory. It automatically creates a + new virtual server automatically picking a server number `auto-servernum`. + The server is passed the arguments using `server-args`, we are telling + Xvfb to create Screen number 0 with width 640, height 480 and depth 24 bits. + Note that 640 X 480 are the default width and height. The main reason for + us to add this is because we'd like to change the depth from the default + of 8 bits to 24. + Unfortunately, this means that we will need to pass the arguments through + a shell which is why we set `shell=True`. Now, this adds its own + complications. E.g SIGINT can bounce off the shell and not get propagated + to the child processes. This is why we add `exec`, so that the shell gets + launched, the arguments are passed to `xvfb-run`. `exec` replaces the shell + we created with `xvfb`. + """ + docker_ls = ("exec xvfb-run --auto-servernum" + " --server-args='-screen 0 640x480x24'" + " {0} --port {1} --seed {2}").format(launch_string, + str(self.port), + str(seed)) + proc1 = subprocess.Popen(docker_ls, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + shell=True) self._socket.settimeout(30) try: try: diff --git a/python/unitytrainers/ppo/trainer.py b/python/unitytrainers/ppo/trainer.py index 401ed9c891..6f50cfd3fc 100755 --- a/python/unitytrainers/ppo/trainer.py +++ b/python/unitytrainers/ppo/trainer.py @@ -214,7 +214,6 @@ def add_experiences(self, curr_all_info: AllBrainInfo, next_all_info: AllBrainIn :param next_all_info: Dictionary of all current brains and corresponding BrainInfo. :param take_action_outputs: The outputs of the take action method. """ - curr_info = curr_all_info[self.brain_name] next_info = next_all_info[self.brain_name] diff --git a/python/unitytrainers/trainer_controller.py b/python/unitytrainers/trainer_controller.py index 546ea3512c..6f79c51003 100644 --- a/python/unitytrainers/trainer_controller.py +++ b/python/unitytrainers/trainer_controller.py @@ -42,10 +42,12 @@ def __init__(self, env_path, run_id, save_freq, curriculum_file, fast_simulation .replace('.x86', '')) # Strip out executable extensions if passed # Recognize and use docker volume if one is passed as an argument if docker_target_name == '': + self.docker_training = False self.model_path = './models/{run_id}'.format(run_id=run_id) self.curriculum_file = curriculum_file self.summaries_dir = './summaries' else: + self.docker_training = True self.model_path = '/{docker_target_name}/models/{run_id}'.format( docker_target_name=docker_target_name, run_id=run_id) @@ -74,7 +76,8 @@ def __init__(self, env_path, run_id, save_freq, curriculum_file, fast_simulation np.random.seed(self.seed) tf.set_random_seed(self.seed) self.env = UnityEnvironment(file_name=env_path, worker_id=self.worker_id, - curriculum=self.curriculum_file, seed=self.seed) + curriculum=self.curriculum_file, seed=self.seed, + docker_training=self.docker_training) self.env_name = os.path.basename(os.path.normpath(env_path)) # Extract out name of environment def _get_progress(self):