From a1e32c067c3bd230681a6764f38fb1bbf44bbc1b Mon Sep 17 00:00:00 2001 From: Marc Skov Madsen Date: Wed, 27 Dec 2023 12:28:22 +0100 Subject: [PATCH] Add Dask How to guide (#4234) --- doc/_static/images/asyncify.png | Bin 0 -> 3902 bytes doc/_static/images/dask-dashboard-empty.png | Bin 0 -> 45389 bytes doc/_static/images/dask_fibonacci_queue.png | Bin 0 -> 33876 bytes doc/_static/logos/dask-logo.svg | 18 ++ doc/how_to/callbacks/async.md | 79 ++++--- doc/how_to/concurrency/async.md | 51 ----- doc/how_to/concurrency/dask.md | 235 ++++++++++++++++++++ doc/how_to/concurrency/index.md | 32 ++- doc/how_to/concurrency/sync_to_async.md | 40 ++++ 9 files changed, 366 insertions(+), 89 deletions(-) create mode 100644 doc/_static/images/asyncify.png create mode 100644 doc/_static/images/dask-dashboard-empty.png create mode 100644 doc/_static/images/dask_fibonacci_queue.png create mode 100644 doc/_static/logos/dask-logo.svg delete mode 100644 doc/how_to/concurrency/async.md create mode 100644 doc/how_to/concurrency/dask.md create mode 100644 doc/how_to/concurrency/sync_to_async.md diff --git a/doc/_static/images/asyncify.png b/doc/_static/images/asyncify.png new file mode 100644 index 0000000000000000000000000000000000000000..527976db87fbf5a9b77221ef4b5494e5ab7d8c70 GIT binary patch literal 3902 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%0D5F!;boz9e(?NnStm z`>1ieV#(SfHA7Sb`b%64TXyEySkF~a4EnS$iGW-FxlwA*2}-#|+%nFR$d7DH#fyGK znDb5o&&Z2esIr-)Dq={0Wu=1hALOufNN*<@Mtuwgo*9RaQsy&7kpTMyal#1{FyH#D ziqg}AV8$QaKu4Pe3qhZUtafbUS+_-33fWBT1A?bxj78?5v z?dxv>zYQL2NYpE&p~d`C3vbuHoov*=rn9VLGn(DgD&kYo0tA8z=LywX13fn$elquQ zsaXJFwfn+nUyx~s!fbKrtGDH=^Q$7irIYbgnX7-hBfy$&lX#h~qP5#fH@9yX<-k)^ z_wEVabWrl7RJ~f&@j8zqUv^>Vd0kZn1>1Xx&YU0Z>`jdkgm1nEhx|}LraO9IGnn>& z*WJ8zl7aU-Zqh|pgrgqub+kIvV*$B6bb62=9^Bb|h`X?mZttWR-f%?SGMDgfw=>A& z=*|YpEQ$K9?zQQh^7*f;Z51+*d^uYR9#}~S={n2EjFr`tZ{Z8>G%a|Ml@Nrih4$$| z&69xJybqn#?h_Ua33h?2qLXdGLN)3}O2J-txjd2_pUt2LM@j*HNC?CAPd>75>FrL6 zw_-2FqZ-nOM!lrGVe%AAP0pc)flgbH@0sY&xTMVHmTGlD(4Su3f$c)fXVpj4wR2}x z=zheh7D#O3s)N4>ue*f=05ortHDTHyBonBbOXReHOdfR%Ia&CK-R)`+HBe8-TZdi< zF48uTUvY47L!5U!W7}ECW1-L63HfF0i7=^>XwRESewTOtIjiWc#x4`tWoZ@bh~9y!5CkT<;l{aQsn zbf2KepkPIyisMuHJ{r)-qxtm=pd**7|LUig_sM&*EJAH2^!H?xL8F_R#C$#>GQBlwY)X7;!{5J$IhMoWb literal 0 HcmV?d00001 diff --git a/doc/_static/images/dask_fibonacci_queue.png b/doc/_static/images/dask_fibonacci_queue.png new file mode 100644 index 0000000000000000000000000000000000000000..6ecbca2760d79819e66f8c8b70b5a707e2723159 GIT binary patch literal 33876 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 E0=>;QZ2$lO literal 0 HcmV?d00001 diff --git a/doc/_static/logos/dask-logo.svg b/doc/_static/logos/dask-logo.svg new file mode 100644 index 0000000000..3ac766541b --- /dev/null +++ b/doc/_static/logos/dask-logo.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/doc/how_to/callbacks/async.md b/doc/how_to/callbacks/async.md index 92156ed266..d43eb3bcfe 100644 --- a/doc/how_to/callbacks/async.md +++ b/doc/how_to/callbacks/async.md @@ -1,13 +1,22 @@ # Use Asynchronous Callbacks -This guide addresses how to leverage asynchronous callbacks to run I/O bound tasks in parallel. +This guide addresses how to leverage asynchronous callbacks to run I/O bound tasks in parallel. This technique is also beneficial for CPU bound tasks that release the GIL. -```{admonition} Prerequisites -1. Python has natively supported asynchronous functions since version 3.5, for a quick overview of some of the concepts involved see [the Python documentation](https://docs.python.org/3/library/asyncio-task.html). +You can use `async` function with event handlers like `on_click` as well as the reactive apis `.bind`, `.depends` and `.watch`. + +You can also schedule asynchronous periodic callbacks with `pn.state.add_periodic_callback` as well as run `async` functions directly with `pn.state.execute`. + +```{admonition} Asyncio +For a quick overview of the most important `asyncio` concepts see [the Python documentation](https://docs.python.org/3/library/asyncio-task.html). +``` + +```{admonition} Bokeh Models +It is important to note that asynchronous callbacks operate without locking the underlying Bokeh Document, which means Bokeh models cannot be safely modified by default. Usually this is not an issue because modifying Panel components appropriately schedules updates to underlying Bokeh models, however in cases where we want to modify a Bokeh model directly, e.g. when embedding and updating a Bokeh plot in a Panel application we explicitly have to decorate the asynchronous callback with `pn.io.with_lock` (see example below). ``` + --- -## `.param.watch` +## `on_click` One of the major benefits of leveraging async functions is that it is simple to write callbacks which will perform some longer running IO tasks in the background. Below we simulate this by creating a `Button` which will update some text when it starts and finishes running a long-running background task (here simulated using `asyncio.sleep`. If you are running this in the notebook you will note that you can start multiple tasks and it will update the text immediately but continue in the background: @@ -30,36 +39,7 @@ button.on_click(run_async) pn.Row(button, text) ``` -Note that `on_click` is simple one way of registering an asynchronous callback, but the more flexible `.param.watch` is also supported. Scheduling asynchronous periodic callbacks can be done with `pn.state.add_periodic_callback`. - -It is important to note that asynchronous callbacks operate without locking the underlying Bokeh Document, which means Bokeh models cannot be safely modified by default. Usually this is not an issue because modifying Panel components appropriately schedules updates to underlying Bokeh models, however in cases where we want to modify a Bokeh model directly, e.g. when embedding and updating a Bokeh plot in a Panel application we explicitly have to decorate the asynchronous callback with `pn.io.with_lock`. - -```{pyodide} -import numpy as np -from bokeh.plotting import figure -from bokeh.models import ColumnDataSource - -button = pn.widgets.Button(name='Click me!') - -p = figure(width=500, height=300) -cds = ColumnDataSource(data={'x': [0], 'y': [0]}) -p.line(x='x', y='y', source=cds) -pane = pn.pane.Bokeh(p) - -@pn.io.with_lock -async def stream(event): - await asyncio.sleep(1) - x, y = cds.data['x'][-1], cds.data['y'][-1] - cds.stream({'x': list(range(x+1, x+6)), 'y': y+np.random.randn(5).cumsum()}) - pane.param.trigger('object') - -# Equivalent to `.on_click` but shown -button.param.watch(stream, 'clicks') - -pn.Row(button, pane) -``` - -## `pn.bind` +## `.bind` ```{pyodide} widget = pn.widgets.IntSlider(start=0, end=10) @@ -80,7 +60,9 @@ pn.Column(widget, pn.bind(get_img, widget)) In this example Panel will invoke the function and update the output when the function returns while leaving the process unblocked for the duration of the `aiohttp` request. -The equivalent can be written using `.param.watch` as: +## `.watch` + +The app from the section above can be written using `.param.watch` as: ```{pyodide} widget = pn.widgets.IntSlider(start=0, end=10) @@ -107,6 +89,33 @@ pn.Column(widget, image) In this example Param will await the asynchronous function and the image will be updated when the request completes. +## Bokeh models with `pn.io.with_lock` + +```{pyodide} +import numpy as np +from bokeh.plotting import figure +from bokeh.models import ColumnDataSource + +button = pn.widgets.Button(name='Click me!') + +p = figure(width=500, height=300) +cds = ColumnDataSource(data={'x': [0], 'y': [0]}) +p.line(x='x', y='y', source=cds) +pane = pn.pane.Bokeh(p) + +@pn.io.with_lock +async def stream(event): + await asyncio.sleep(1) + x, y = cds.data['x'][-1], cds.data['y'][-1] + cds.stream({'x': list(range(x+1, x+6)), 'y': y+np.random.randn(5).cumsum()}) + pane.param.trigger('object') + +# Equivalent to `.on_click` but shown +button.param.watch(stream, 'clicks') + +pn.Row(button, pane) +``` + ## Related Resources - See the related [How-to > Link Parameters with Callbacks API](../links/index.md) guides, including [How to > Create Low-Level Python Links Using `.watch`](../links/watchers.md). diff --git a/doc/how_to/concurrency/async.md b/doc/how_to/concurrency/async.md deleted file mode 100644 index 7fb7494bab..0000000000 --- a/doc/how_to/concurrency/async.md +++ /dev/null @@ -1,51 +0,0 @@ -# Use Asynchronous Processing - -When using Python you can use async callbacks wherever you would ordinarily use a regular synchronous function. For instance you can use `pn.bind` on an async function: - -```{pyodide} -import panel as pn - -widget = pn.widgets.IntSlider(start=0, end=10) - -async def get_img(index): - url = f"https://picsum.photos/800/300?image={index}" - if pn.state._is_pyodide: - from pyodide.http import pyfetch - return pn.pane.JPG(await (await pyfetch(url)).bytes()) - - import aiohttp - async with aiohttp.ClientSession() as session: - async with session.get(url) as resp: - return pn.pane.JPG(await resp.read()) - -pn.Column(widget, pn.bind(get_img, widget)) -``` - -In this example Panel will invoke the function and update the output when the function returns while leaving the process unblocked for the duration of the `aiohttp` request. - -Similarly you can attach asynchronous callbacks using `.param.watch`: - -```{pyodide} -widget = pn.widgets.IntSlider(start=0, end=10) - -image = pn.pane.JPG() - -async def update_img(event): - url = f"https://picsum.photos/800/300?image={event.new}" - if pn.state._is_pyodide: - from pyodide.http import pyfetch - image.object = await (await pyfetch(url)).bytes() - return - - import aiohttp - async with aiohttp.ClientSession() as session: - async with session.get(url) as resp: - image.object = await resp.read() - -widget.param.watch(update_img, 'value') -widget.param.trigger('value') - -pn.Column(widget, image) -``` - -In this example Param will await the asynchronous function and the image will be updated when the request completes. diff --git a/doc/how_to/concurrency/dask.md b/doc/how_to/concurrency/dask.md new file mode 100644 index 0000000000..bd015acf00 --- /dev/null +++ b/doc/how_to/concurrency/dask.md @@ -0,0 +1,235 @@ +# Scaling with Dask + +This guide demonstrates how you can *offload tasks* to [Dask](https://www.dask.org/) to **scale your apps to bigger datasets, bigger calculations and more users**. + + + +Panel supports `async` and `await`. This means you can easily **offload large computations to your Dask cluster asynchronously and keep your app responsive while you `await` the results**. Please note that off loading the computations to the Dask cluster can add ~250msec of overhead and thus is not suitable for all kinds of use cases. + +## Installation + +Lets start by installing *Panel*, *hvPlot* and *Dask Distributed*. + +```bash +pip install panel hvplot dask[distributed] +``` + +## Start the Cluster + +For development, testing and many use cases a [`LocalCluster`](https://docs.dask.org/en/stable/deploying-python.html#localcluster) is more than fine and will allow you to leverage all the CPUs on your machine. When you want to scale out to an entire cluster will you can switch to a non-local cluster. To avoid any issues when combining Panel and Dask we recommend starting the `LocalCluster` +separately from the Dask `Client` and your Panel app. + +```python +# cluster.py +from dask.distributed import LocalCluster + +DASK_SCHEDULER_PORT = 64719 +DASK_SCHEDULER_ADDRESS = f"tcp://127.0.0.1:{DASK_SCHEDULER_PORT}" +N_WORKERS = 4 + +if __name__ == '__main__': + cluster = LocalCluster(scheduler_port=DASK_SCHEDULER_PORT, n_workers=N_WORKERS) + print(cluster.scheduler_address) + input() +``` + +and running + +```bash +$ python cluster.py +tcp://127.0.0.1:64719 +``` + +You can now open the [Dask Dashboard](https://docs.dask.org/en/stable/dashboard.html) at [http://localhost:8787/status](http://localhost:8787/status). + +So far there is not a lot to see here: + +![Empty Dask Dashboard](../../_static/images/dask-dashboard-empty.png) + +The Dask `Client` will serialize any *tasks* and send them to the Dask `Cluster` for execution. This means that the `Client` and `Cluster` must able to import the same versions of all *tasks* and python package dependencies. + +## Dask Distributed + +## Fibonacci Task Queue + +In this section we will define a Panel app to *submit* and *monitor* Fibonacci tasks. + +Let's start by defining the *fibonacci* tasks in a `tasks.py` file: + +```python +# tasks.py +from datetime import datetime as dt + +import numpy as np + + +def _fib(n): + if n < 2: + return n + else: + return _fib(n - 1) + _fib(n - 2) + + +def fibonacci(n): + start = dt.now() + print(start, "start", n) + result = _fib(n) + end = dt.now() + print(end, "end", (end-start).seconds, n, result) + return result +``` + +Lets now define the full `app.py` file. + +```python +# app.py +from datetime import datetime as dt + +from dask.distributed import Client + +import panel as pn + +from cluster import DASK_SCHEDULER_ADDRESS +from tasks import fibonacci + +QUEUE = [] + +pn.extension("terminal", design="material", sizing_mode="stretch_width") + +@pn.cache # We use caching to share the client across all users and sessions +async def get_client(): + return await Client( + DASK_SCHEDULER_ADDRESS, asynchronous=True + ) + +n_input = pn.widgets.IntInput(value=0, width=100, sizing_mode="fixed", name="n") +submit_button = pn.widgets.Button(name="SUBMIT", button_type="primary", align="end") +terminal_widget = pn.widgets.Terminal( + height=200, +) + +queue = pn.rx(QUEUE) + +@pn.depends(submit_button, watch=True) +async def _handle_click(_): + n = n_input.value + n_input.value += 1 + + start = dt.now() + QUEUE.append(n) + queue.rx.value = QUEUE + + client = await get_client() + fib_n = await client.submit(fibonacci, n) + + end = dt.now() + + QUEUE.pop(QUEUE.index(n)) + queue.rx.value = QUEUE + + duration = (end - start).seconds + terminal_widget.write(f"fibonacci({n})={fib_n} in {duration}sec\n") + + +pn.Column( + "# Fibonacci Tasks", + pn.Row(n_input, submit_button), + pn.rx("## Task queue: {}").format(queue), + "## Results", + terminal_widget, +).servable() +``` + +You can now run `panel serve app.py` and the app will look like + + + +## Dask Dashboard Components + +It can be very useful to include some of the live [Dask endpoints](https://distributed.dask.org/en/stable/http_services.html) in your app. Its easy to do by embedding the specific urls in an *iframe*. + +In the `dashboard.py` file we define the `DaskViewer` component that can be used to explore the *individual dask plots*. + +```python +# dashboard.py +import os + +import param + +import panel as pn + +DASK_DASHBOARD_ADDRESS = os.getenv("DASK_DASHBOARD", "http://localhost:8787/status") + +VIEWS = { + "aggregate-time-per-action": "individual-aggregate-time-per-action", + "bandwidth-types": "individual-bandwidth-types", + "bandwidth-workers": "individual-bandwidth-workers", + "cluster-memory": "individual-cluster-memory", + "compute-time-per-key": "individual-compute-time-per-key", + "cpu": "individual-cpu", + "exceptions": "individual-exceptions", + "gpu-memory": "individual-gpu-memory", + "gpu-utilization": "individual-gpu-utilization", + "graph": "individual-graph", + "groups": "individual-groups", + "memory-by-key": "individual-memory-by-key", + "nprocessing": "individual-nprocessing", + "occupancy": "individual-occupancy", + "profile-server": "individual-profile-server", + "profile": "individual-profile", + "progress": "individual-progress", + "scheduler-system": "individual-scheduler-system", + "task-stream": "individual-task-stream", + "workers-cpu-timeseries": "individual-workers-cpu-timeseries", + "workers-disk-timeseries": "individual-workers-disk-timeseries", + "workers-disk": "individual-workers-disk", + "workers-memory-timeseries": "individual-workers-memory-timeseries", + "workers-memory": "individual-workers-memory", + "workers-network-timeseries": "individual-workers-network-timeseries", + "workers-network": "individual-workers-network", + "workers": "individual-workers", +} + +VIEWER_PARAMETERS = ["url", "path"] + +def dask_dashboard_view(path="individual-cpu", url=DASK_DASHBOARD_ADDRESS): + url = url.replace("/status", "/") + path + return f"""""" + +class DaskViewer(pn.viewable.Viewer): + url = param.String(DASK_DASHBOARD_ADDRESS, doc="The url of the Dask status dashboard") + path = param.Selector(default="individual-cpu", objects=VIEWS, doc="the endpoint", label="View") + + def __init__(self, size=20, **params): + viewer_params = {k:v for k, v in params.items() if k in VIEWER_PARAMETERS} + layout_params = {k:v for k, v in params.items() if k not in VIEWER_PARAMETERS} + + super().__init__(**viewer_params) + + view = pn.bind(dask_dashboard_view, self.param.path, self.param.url) + self._iframe = pn.pane.HTML(view, sizing_mode="stretch_both") + self._select = pn.widgets.Select.from_param(self.param.path, size=size, width=300, sizing_mode="fixed", margin=(20,5,10,5)) + self._link = pn.panel(f"""Dask Dashboard""", height=50, margin=(0,20)) + self._panel = pn.Column(pn.Row(self._iframe, self._select, sizing_mode="stretch_both"), self._link, **layout_params) + + def __panel__(self): + return self._panel + +if __name__.startswith("bokeh"): + pn.extension(sizing_mode="stretch_width") + + DaskViewer(height=500, size=25).servable() +``` + +Try running `panel serve dashboard.py`. If your Dask cluster is working, you will see something like + +![Dask Viewer](https://assets.holoviz.org/panel/how_to/concurrency/dask-dashboard.gif) + +## Additional Resources + +- [Panel - Use Async Callbacks](../callbacks/async.md) +- [Dask - Async/Await and Non-Blocking Execution Documentation](https://examples.dask.org/applications/async-await.html#Async/Await-and-Non-Blocking-Execution) +- [Dask - Async Web Server](https://examples.dask.org/applications/async-web-server.html) diff --git a/doc/how_to/concurrency/index.md b/doc/how_to/concurrency/index.md index 03be0a31c6..7e965bb0ef 100644 --- a/doc/how_to/concurrency/index.md +++ b/doc/how_to/concurrency/index.md @@ -54,10 +54,33 @@ Discover how to manually set up a Thread to process an event queue. ::: :::{grid-item-card} {octicon}`arrow-switch;2.5em;sd-mr-1 sd-animate-grow50` Use Asynchronous Processing -:link: async +:link: ../callbacks/async :link-type: doc -Discover how to make use of asynchronous callbacks to handle I/O bound operations concurrently. +Discover how to make use of asynchronous callbacks to handle I/O and cpu bound operations concurrently. +::: + +:::{grid-item-card} {octicon}`paper-airplane;2.5em;sd-mr-1 sd-animate-grow50` Sync to Async +:link: sync_to_async +:link-type: doc + +Discover how to run your sync callbacks asynchronously to handle I/O and cpu bound operations concurrently. +::: + +:::: + +## Scaling via an external compute engine + +You can also scale your application by offloading your compute heavy tasks to an external compute engine like [Dask](https://www.dask.org/). Please note that this may add additional overhead of several 100ms to your tasks. + +::::{grid} 1 2 2 2 +:gutter: 1 1 1 2 + +:::{grid-item-card} {octicon}`versions;2.5em;sd-mr-1 sd-animate-grow50` Dask +:link: dask +:link-type: doc + +Discover how-to configure and use Dask to scale your Panel application ::: :::: @@ -70,5 +93,8 @@ Discover how to make use of asynchronous callbacks to handle I/O bound operation load_balancing processes threading -async +manual_threading +../callbacks/async +sync_to_async +dask ``` diff --git a/doc/how_to/concurrency/sync_to_async.md b/doc/how_to/concurrency/sync_to_async.md new file mode 100644 index 0000000000..cd80c3da17 --- /dev/null +++ b/doc/how_to/concurrency/sync_to_async.md @@ -0,0 +1,40 @@ +# Run synchronous functions asynchronously + +Running your bound, synchronous functions asynchronously can be an easy way to make your application responsive and scale to more users. + +## Asyncify + +This example will show how to make your app responsive by running a sync, cpu bound function asynchronously. We will be using [asyncer](https://asyncer.tiangolo.com) by Tiangolo. You can install the package via `pip install asyncer`. + +```python +import numpy as np +import pandas as pd + +from asyncer import asyncify +import panel as pn + +widget = pn.widgets.IntSlider(value=5, start=0, end=10) + +def do_sync_work(it, n): + return sum(pd.DataFrame(np.random.rand(n,n)).sum().sum() for _ in range(it)) + +async def create_result(): + yield pn.indicators.LoadingSpinner(value=True, width=25, height=25) + result = await asyncify(do_sync_work)(it=5, n=10000) + yield f"Wow. That was slow.\n\nThe sum is **{result:.2f}**" + +pn.Column(widget.rx() + 1, create_result).servable() +``` + + + +Without [`asyncify`](https://asyncer.tiangolo.com/tutorial/asyncify/) the app would have been unresponsive for 5-10 seconds while loading. + +[`asyncify`](https://asyncer.tiangolo.com/tutorial/asyncify/) works well for IO bound functions as well as for CPU bound functions that releases the GIL. + +## Dask + +If you run many cpu bound functions you may consider offloading your functions asynchronously to an external compute engine like [Dask](https://www.dask.org/). See our [Dask how-to Guide](../performance/dask.md).