From 1a997ed27989c86629edea8ae198422c7be455df Mon Sep 17 00:00:00 2001 From: Robert Nishihara Date: Mon, 27 Feb 2017 21:14:31 -0800 Subject: [PATCH] Move documentation to ReadTheDocs. (#326) --- .travis.yml | 8 ++ README.md | 57 +--------- doc/Makefile | 4 +- doc/README.md | 15 +++ doc/api.rst | 15 --- doc/environment-variables.md | 106 ------------------ doc/figures/compgraph1.png | Bin 4914 -> 0 bytes doc/figures/compgraph2.png | Bin 2645 -> 0 bytes doc/index.rst | 16 --- doc/install-on-windows.md | 26 ----- doc/requirements-doc.txt | 10 ++ doc/{ => source}/_static/.gitkeep | 0 doc/{ => source}/_templates/.gitkeep | 0 doc/source/api.rst | 10 ++ doc/{ => source}/conf.py | 14 +-- .../source/example-hyperopt.md | 0 .../README.md => doc/source/example-lbfgs.md | 0 .../source/example-rl-pong.md | 0 doc/source/index.rst | 45 ++++++++ doc/{ => source}/install-on-docker.md | 0 doc/{ => source}/install-on-macosx.md | 2 +- doc/{ => source}/install-on-ubuntu.md | 2 +- .../installation-troubleshooting.md | 0 doc/{ => source}/remote-functions.md | 0 doc/{ => source}/serialization.md | 2 +- doc/{ => source}/tutorial.md | 0 doc/{ => source}/using-ray-on-a-cluster.md | 0 .../using-ray-on-a-large-cluster.md | 0 doc/{ => source}/using-ray-with-tensorflow.md | 0 python/ray/worker.py | 8 +- src/common/thirdparty/python | 1 - src/common/thirdparty/redis-windows | 1 - 32 files changed, 106 insertions(+), 236 deletions(-) create mode 100644 doc/README.md delete mode 100644 doc/api.rst delete mode 100644 doc/environment-variables.md delete mode 100644 doc/figures/compgraph1.png delete mode 100644 doc/figures/compgraph2.png delete mode 100644 doc/index.rst delete mode 100644 doc/install-on-windows.md create mode 100644 doc/requirements-doc.txt rename doc/{ => source}/_static/.gitkeep (100%) rename doc/{ => source}/_templates/.gitkeep (100%) create mode 100644 doc/source/api.rst rename doc/{ => source}/conf.py (96%) rename examples/hyperopt/README.md => doc/source/example-hyperopt.md (100%) rename examples/lbfgs/README.md => doc/source/example-lbfgs.md (100%) rename examples/rl_pong/README.md => doc/source/example-rl-pong.md (100%) create mode 100644 doc/source/index.rst rename doc/{ => source}/install-on-docker.md (100%) rename doc/{ => source}/install-on-macosx.md (98%) rename doc/{ => source}/install-on-ubuntu.md (99%) rename doc/{ => source}/installation-troubleshooting.md (100%) rename doc/{ => source}/remote-functions.md (100%) rename doc/{ => source}/serialization.md (99%) rename doc/{ => source}/tutorial.md (100%) rename doc/{ => source}/using-ray-on-a-cluster.md (100%) rename doc/{ => source}/using-ray-on-a-large-cluster.md (100%) rename doc/{ => source}/using-ray-with-tensorflow.md (100%) delete mode 160000 src/common/thirdparty/python delete mode 160000 src/common/thirdparty/redis-windows diff --git a/.travis.yml b/.travis.yml index 853e83b74..4c9a4866d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,6 +28,14 @@ matrix: install: [] script: - .travis/check-git-clang-format-output.sh + # Try generating Sphinx documentation. To do this, we need to setup some + # Python stuff. + - wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh + - bash miniconda.sh -b -p $HOME/miniconda + - export PATH="$HOME/miniconda/bin:$PATH" + - cd doc + - pip install -r requirements-doc.txt + - sphinx-build -W -b html -d _build/doctrees source _build/html - os: linux dist: trusty env: VALGRIND=1 PYTHON=2.7 diff --git a/README.md b/README.md index afabb51ed..9e9a43d64 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # Ray [![Build Status](https://travis-ci.org/ray-project/ray.svg?branch=master)](https://travis-ci.org/ray-project/ray) +[![Documentation Status](https://readthedocs.org/projects/ray/badge/?version=latest)](http://ray.readthedocs.io/en/latest/?badge=latest) Ray is an experimental distributed execution engine. It is under development and not ready to be used. @@ -9,58 +10,4 @@ The goal of Ray is to make it easy to write machine learning applications that run on a cluster while providing the development and debugging experience of working on a single machine. -Before jumping into the details, here's a simple Python example for doing a -Monte Carlo estimation of pi (using multiple cores or potentially multiple -machines). - -```python -import ray -import numpy as np - -# Start Ray with some workers. -ray.init(num_workers=10) - -# Define a remote function for estimating pi. -@ray.remote -def estimate_pi(n): - x = np.random.uniform(size=n) - y = np.random.uniform(size=n) - return 4 * np.mean(x ** 2 + y ** 2 < 1) - -# Launch 10 tasks, each of which estimates pi. -result_ids = [] -for _ in range(10): - result_ids.append(estimate_pi.remote(100)) - -# Fetch the results of the tasks and print their average. -estimate = np.mean(ray.get(result_ids)) -print("Pi is approximately {}.".format(estimate)) -``` - -Within the for loop, each call to `estimate_pi.remote(100)` sends a message to -the scheduler asking it to schedule the task of running `estimate_pi` with the -argument `100`. This call returns right away without waiting for the actual -estimation of pi to take place. Instead of returning a float, it returns an -**object ID**, which represents the eventual output of the computation (this is -a similar to a Future). - -The call to `ray.get(result_id)` takes an object ID and returns the actual -estimate of pi (waiting until the computation has finished if necessary). - -## Next Steps - -- Installation on [Ubuntu](doc/install-on-ubuntu.md), [Mac OS X](doc/install-on-macosx.md) - - [Troubleshooting](doc/installation-troubleshooting.md) -- [Tutorial](doc/tutorial.md) -- Documentation - - [Serialization in the Object Store](doc/serialization.md) - - [Environment Variables](doc/environment-variables.md) - - [Using Ray with TensorFlow](doc/using-ray-with-tensorflow.md) - - [Using Ray on a Cluster](doc/using-ray-on-a-cluster.md) - - [Example Management Using Parallel-SSH](doc/using-ray-on-a-large-cluster.md) - -## Example Applications - -- [Hyperparameter Optimization](examples/hyperopt/README.md) -- [Batch L-BFGS](examples/lbfgs/README.md) -- [Learning to Play Pong](examples/rl_pong/README.md) +View the [documentation](http://ray.readthedocs.io/en/latest/index.html). diff --git a/doc/Makefile b/doc/Makefile index 6943059d5..e188f9a06 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -15,9 +15,9 @@ endif # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source # the i18n builder cannot share the environment and doctrees with the others -I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext diff --git a/doc/README.md b/doc/README.md new file mode 100644 index 000000000..f001d0d68 --- /dev/null +++ b/doc/README.md @@ -0,0 +1,15 @@ +# Ray Documentation + +To compile the documentation, run the following commands from this directory. + +``` +pip install -r requirements-doc.txt +make html +open _build/html/index.html +``` + +To test if there are any build errors with the documentation, do the following. + +``` +sphinx-build -W -b html -d _build/doctrees source _build/html +``` diff --git a/doc/api.rst b/doc/api.rst deleted file mode 100644 index b6c092dcc..000000000 --- a/doc/api.rst +++ /dev/null @@ -1,15 +0,0 @@ -=========== -The Ray API -=========== - -.. autofunction:: ray.put -.. autofunction:: ray.get -.. autofunction:: ray.remote -.. autofunction:: ray.wait -.. autofunction:: ray.init -.. autofunction:: ray.kill_workers -.. autofunction:: ray.restart_workers_local -.. autofunction:: ray.visualize_computation_graph -.. autofunction:: ray.scheduler_info -.. autofunction:: ray.task_info -.. autofunction:: ray.register_module diff --git a/doc/environment-variables.md b/doc/environment-variables.md deleted file mode 100644 index d695dba19..000000000 --- a/doc/environment-variables.md +++ /dev/null @@ -1,106 +0,0 @@ -# Environment Variables - -This document explains how to create and use **environment variables** in Ray. - -An environment variable is a per-worker variable which is (1) created when the -worker starts, and (2) is reinitialized before a task reuses it. Thus, while a -task can modify an environment variable, the variable is reinitialized before -the next task uses it. Environment variables obviates the need for -serialization/deserialization and help avoid side effects. - -Environment variables are Python objects that are created once on each worker -and can be used by all subsequent tasks that run on that worker. Environment -variables will be reinitialized between tasks. There are several primary reasons -for using environment variables. - -1. Environment variables are created once on each worker and are not shipped -between machines, so they do not need to be serialized or deserialized (however, -the code that creates the environment variable does need to be pickled). -2. Objects that are slow to construct (like a TensorFlow graph) only need to be -constructed once on each worker. -3. By reinitializing between tasks that use them, they help avoid side effects. - -To elaborate on the first point, standard Python serialization libraries like -pickle fail on some objects. With these kinds of objects, it may be easier to -ship the code that creates the object to each worker and to run the code on each -worker than it would be to create the object on the driver and ship the object -to each worker. - -## Creating an Environment Variable - -To give an example, consider a gym environment, which essentially provides a -Python wrapper for an Atari simulator. - -```python -import gym -import ray - -ray.init(num_workers=10) - -# Define a function to create the gym environment. -def env_initializer(): - return gym.make("Pong-v0") - -# Create the environment variable. This line will cause env_initializer to run -# on each worker and on the driver. -ray.env.env = ray.EnvironmentVariable(env_initializer) - -# Define a remote function that uses the gym environment. -@ray.remote -def step(): - env = ray.env.env - # Choose a random action. - action = env.action_space.sample() - # Take the action and return the result. - return env.step(action) - -# Call the remote function. -step.remote() -``` - -When the gym is created, it prints something like `Making new env: Pong-v0`. You -may notice that this is printed once for each worker. Calling `step.remote()` -will run a remote function that uses the `env` variable. You may notice that -calling `step.remote()` causes the line `Making new env: Pong-v0` to be printed -again. That occurs because, by default, every time a remote function uses an -environment variable, the worker will rerun the code that initializes the -environment variable to prevent side effects from leaking between tasks and -introducing non-determinism into the program. - -Of course, rerunning the initialization code can be expensive, so a custom -reinitializer can be passed into the creation of an environment variable. If the -state of the environment variable is not mutated by any remote function, then -the reinitialization code can just be the identity function. - -```python -# Define a function to create the gym environment. -def env_initializer(): - return gym.make("Pong-v0") - -# Define a function to reinitialize the gym environment. -def env_reinitializer(env): - env.reset() - return env - -# Create the environment variable. This line will cause env_initializer to run -# on each worker and on the driver. Every time a remote function uses the -# environment variable, env_reinitializer will run to reset the state of the -# variable. -ray.env.env = ray.EnvironmentVariable(env_initializer, env_reinitializer) - -# Define a remote function that uses the gym environment. -@ray.remote -def step(): - env = ray.env.env - # Choose a random action. - action = env.action_space.sample() - # Take the action and return the result. - return env.step(action) - -# Call the remote function. -step.remote() -``` - -**Note:** It may sometimes look like Ray is hanging and not responding. This can -occur when print statements happen in the background on workers and hide the -interpreter prompt. Try pressing enter, and see if that fixes it. diff --git a/doc/figures/compgraph1.png b/doc/figures/compgraph1.png deleted file mode 100644 index bc34726b07f488ab7518f30e83e9ab5e7003ff41..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4914 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 diff --git a/doc/figures/compgraph2.png b/doc/figures/compgraph2.png deleted file mode 100644 index 92309630483a9e1da3dc03441fa0b24f14dec0f1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2645 zcmV-b3aa&qP)~&2Ae1l z#4|rcHPxid?y6aHz4ZsiBTMGb-OZ}%4|bOsBauiX5{X12kw_#Gi9{liNF)-8^v9Kz zhy)2G5|Lt|9{+KSa`EabM!CGIJQ%1;{QJnkhHykdtLPnHBBDkrj)+P@-q*UD>5Fdk z@k*EnVl;E;i0H|0T^$m(9HXn=;5m4P7XCodsju5YviKpM`8*N+#4|HAl!u3kH_wRj zdr@0m)Uxk`)lL^R@B1L%p;>6YN3&2&RE}4{9L)(=cqlJV@oK1tIv00Wdj5Q_kN5PP z&^=e;r=2j)TPlBd`>nLHD_LJ16?J^uzFDbqLpY%bMW{X$&lFTW%F;%;k%AJ2D92Y& zp&=>=6cjf^`FNl#!ullMG4{F`oSLJo8pSbCJpQgmMR3$d3AF{GnhF-E6xvqT2h`r&M8)-Y zk0^fqBd?5MV~*nM8}|`)cO%?41u6kJ&JW@P^Q;Gc>lKbOgACR7V#~txt+n&uqdpsN z0NH{16Z6)|<^JTHTrib!&@y)-#z5XDf;>~{Y<-hhqyHwM^{jj^ccjQui zz6pO7PIMOKb+py;?&13G;Zj0b58_sccdyQ(!c;|V?jF|3Ln5L0y|{Jxt$sh4^*+%>Sy{^0$!GZviZydxev|5=cv_&corLmufm(00QAvq`JKFH+Q#!e?QelxtH7f>+D`eO}}|?#oEzEO`O$z z@@4%_LM{KAyZoaY>7v42>}7GeJaj;e z3Z`RhtFLGb>i#?9?Th0?#p-f`=~mBiqC&M{R@;|^^3;YIyc^m6YAB(i!l9@WXBBG0 z)Q|grQ`9M0MVW3*=W4@v>9eCO6=iy4(o!47^dRRbQ6kSzts$&eb9L1$10Qc`@s>fk z!bcAj?J%=?yrrQ*m?@)iEZ=;-Yu!&&>>4$|{t3U^}P^K&LRKwOm zWy4o6iD zQ=bJWOHvo7?j3DiRK0mvMsY(_eHNf}lQv4|28Px~>lR95VxOugrj2@qhw|}I0UpZ1 zLoq@bp|sBe^rN)T0-T8qzUt1}gS84(p9PSEj4%u`bwF-~zDyU@>Z#;_+|^I&pqPRh z)=l_I=f%~|YZxlP>rx%1%XHPDtVT!gsNY0}!w*}HhJMdb^~o6k!cdKM3?L)blr&~0 zmO2)-b;qJzzcy2XfmSN3OeunVxz;Rhp%g)`l&gb6oa0Q?m&=aX@DsW`X^oj$l#Pzl zNR7Nu8fdUB*zM_i^XfPU9=slAx0{2jivwCW@Yq7qM1G@uHcXy+-AuRDPuCJQ3SB!} z%2rtxIxHTAy6bbXdfo1H?PzFYm1VHx)%!R(uH5O!+8==jd)gj>dNte>&$wD|6cEG9y5MLi8xO@sio!Edfrqm3P!1l-$3sP8i!i;BXi8KDS8 zC_)j6P=q2Bp$J7NLJ^8kFCXOr_fn1?C4f6TM~~tdyR65Ig6@X>Iw*#TVq-+D@KA3@ ziF%EPn!Rw;lYfG3u!_~+9SW)3c8?Qg%Jf+RV=0>L(R`=gjxG>tSZ7AG5NddZ=R8f` zT5u$7_Q-Q^V|C=-G1W1T68N<2vI7cib@(Z4@G*1Nbv^W8Dq^Al#7qc?sU8q>SuA=g zYoM&>78po20FGs$;3P0fA6MVldut0acA&Z2t@u8NqYh^QKzWKP13#Pvh!9ZqSpWpo z;Vi%u9vJ8>fCh@r0t`pdS%AUrd^ig*MF=uA3L(fG&H}u^#Bl--h1jKXypA3*2VHwm z1VbSX)u7qldIs3Qv8dlO@CJ_CfwMRl%LQ?hp`~3fp@s!KX^xtet0M(2d~sj_MRoiw zA>%NUVYT{kp#i+ia7@jMu;?VJ8uAQ|s?+?&@TRJBSy(8mndM(Ci!%%qTjdOxx8)cT z@*M3!4&oyr5{X12kw_#Gi9{liNF)-8L?V$$zaRY{