Skip to content

Start queue when gradio is a sub application#2319

Merged
freddyaboulton merged 15 commits into
mainfrom
fix-queue-when-app-mounted
Sep 23, 2022
Merged

Start queue when gradio is a sub application#2319
freddyaboulton merged 15 commits into
mainfrom
fix-queue-when-app-mounted

Conversation

@freddyaboulton
Copy link
Copy Markdown
Collaborator

@freddyaboulton freddyaboulton commented Sep 21, 2022

Description

Closes: #2292

There were two problems as noted on the original issue:

  1. The ws socket path was incorrect when running as a sub application
  2. The thread to run the queue was not started because that's started by the startup-events route that's called during Blocks.launch.

I think I figured out the ws path issue although I'd like some feedback from @aliabid94 .

The queue thread not starting automatically is an interesting problem. I think the cleanest thing would be to use fastapi startup events to start the queue however there are two issues with that:

  • Startup events are not started for sub applications (docs) so it wouldn't solve the problem of the queue not starting when gradio is mounted
  • The queue needs to know about the URL where the gradio app is running in order to get the predictions. Since startup events happen before the app is running, the URL is not defined when the event runs.

I opted for adding a helper method called mount_gradio_app that will add a startup event to the parent application that runs the queue. This is not as nice as using the built-in FastAPI app.mount(gradio_app, ...) syntax but it's the best solution I see now.

Changes to the guide

image

Checklist:

  • I have performed a self-review of my own code
  • My code follows the style guidelines of this project
  • I have commented my code in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes

@freddyaboulton freddyaboulton force-pushed the fix-queue-when-app-mounted branch from 1853b1d to e3da8e3 Compare September 21, 2022 19:49
@github-actions
Copy link
Copy Markdown
Contributor

All the demos for this PR have been deployed at https://huggingface.co/spaces/gradio-pr-deploys/pr-2319-all-demos

Comment thread ui/packages/app/src/api.ts
Comment thread gradio/blocks.py
@freddyaboulton freddyaboulton changed the title Fix queue when app mounted Start queue when gradio is a sub application Sep 21, 2022
@freddyaboulton freddyaboulton marked this pull request as ready for review September 21, 2022 21:21
@abidlabs
Copy link
Copy Markdown
Member

Nice PR @freddyaboulton! I had a question about supplying the server_name and port to the mount_gradio_app() function. It would be great if the user didn't have to supply that manually and ensure its consistency with what's written on the terminal.

Do we really need to know the absolute path for the local server? It seems like that is used inside the Queue.call_prediction() function, but it's unclear to me why we need the absolute path here.

@freddyaboulton
Copy link
Copy Markdown
Collaborator Author

@abidlabs I agree it's not ideal to have to specify the server and port in two places. I think mount_gradio_app needs the full url so that it can call gradio_app.blocks._queue.set_url. Otherwise the queue won't know which url to send the post requests to in call_prediction.

Do you have other solutions in mind?

@aliabid94
Copy link
Copy Markdown
Contributor

websocket part LGTM.

@abidlabs
Copy link
Copy Markdown
Member

@abidlabs I agree it's not ideal to have to specify the server and port in two places. I think mount_gradio_app needs the full url so that it can call gradio_app.blocks._queue.set_url. Otherwise the queue won't know which url to send the post requests to in call_prediction.

I might be missing something but can't the queue just use the relative URL like all of the other route? Basically here:

url=f"{self.server_path}api/predict",

Why can't just be:

url="/api/predict",

@freddyaboulton
Copy link
Copy Markdown
Collaborator Author

@abidlabs The queue is running on a separate thread than the main gradio app so it needs the full url path to the predict endpoint to access the gradio api.

Just tried out your suggestion and I got an error with the blocks_essay demo with queue enabled:

image

@abidlabs
Copy link
Copy Markdown
Member

Gotcha, testing the PR now

Comment thread gradio/utils.py Outdated
Comment thread gradio/utils.py Outdated
return type(val) is dict and "update" in val.get("__type__", "")


def mount_gradio_app(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit/suggestion: it feels more natural to me for this method to belong in routes.py with all of the other fastapi-related stuff

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

Comment thread demo/custom_path/run.py Outdated

io = gr.Interface(lambda x: "Hello, " + x + "!", "textbox", "textbox")
gradio_app = gr.routes.App.create_app(io)
app = gr.mount_gradio_app(app, gradio_app, server_name="localhost", port="8000", path="/gradio")
Copy link
Copy Markdown
Member

@abidlabs abidlabs Sep 23, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A suggestion for this function: can we have it take a Gradio Blocks object instead of a FastAPI app as its second parameter? And then internally it calls it gr.routes.App.create_app(io). This saves our users a line of code and needing to reference two different methods both of which are deep in our code base

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure thing! Good point.

@abidlabs
Copy link
Copy Markdown
Member

abidlabs commented Sep 23, 2022

@freddyaboulton I tested and this PR works great, and allows a FastAPI app to mount a Gradio app with or without queue enabled.

My main suggestion would be to try to simplify the usage for our users. I noticed that there is actually a way to get the server URL without needing the user to specify it. In any FastAPI route, if you add the request: Request parameter, you can access the server's URL with request.url._url (or str(request.url) which may be better since it doesn't require accessing a private variable). Similarly, for a websocket route, you can do websocket.url._url.

So this suggests an idea:

  1. When the FastAPI server first launches, set _queue.server_path=None (this is already the case)
  2. In the @app.websocket("/queue/join") route, check the queue's server_path. If it is currently None, then set it to a stripped down version of websocket.url._url

This should eliminate the need for the server_name and port parameters from mount_gradio_app()

@freddyaboulton freddyaboulton force-pushed the fix-queue-when-app-mounted branch from 363131b to 7af2f04 Compare September 23, 2022 15:42
@freddyaboulton
Copy link
Copy Markdown
Collaborator Author

Thanks for the awesome suggestion @abidlabs ! I agree that it's much better to not have users specify the same information twice. I pushed up the suggestion you made - we can now set the gradio api endpoint dynamically from the queue join route if it's not specified.

This works! I deployed a fastapi app with gradio as a sub application here: https://gradio-sub-app.onrender.com/

If you go to https://gradio-sub-app.onrender.com/gradio/ you will see a gradio app!

image

You can also access the lion image here: https://gradio-sub-app.onrender.com/lion.jpg (I mounted a static file app on the root path).

The problem with this approach is that it won't work on spaces because spaces only expose websockets on a specific endpoint that's different from the one gradio is running on, e.g. wss://spaces.huggingface.tech/space-name/queue/join.

What if we keep an optional parameter called gradio_api_endpoint to mount_gradio_app that let's users manually set the gradio endpoint if they desire.

With that approach, I was able to get gradio sub-applications to work on spaces here: https://huggingface.co/spaces/freddyaboulton/gradio-subapp

@abidlabs
Copy link
Copy Markdown
Member

Thank you so much for trying out the suggestions @freddyaboulton!

What if we keep an optional parameter called gradio_api_endpoint to mount_gradio_app that let's users manually set the gradio endpoint if they desire.

Sounds good! Let's add and document this parameter :)

@freddyaboulton
Copy link
Copy Markdown
Collaborator Author

@abidlabs Just pushed up the doc changes!

Documentation here:

image

Updated guide here:

image

Should be good for a final look now!

Comment thread gradio/routes.py Outdated
app: fastapi.FastAPI,
blocks: gradio.Blocks,
path: str,
gradio_api_url: Optional[str],
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
gradio_api_url: Optional[str],
gradio_api_url: Optional[str] = None,

@abidlabs
Copy link
Copy Markdown
Member

Works great @freddyaboulton. I'm going to push some small tweaks to the documentation (easier than commenting in all of the places) if you don't mind! Feel free to take or leave

@abidlabs
Copy link
Copy Markdown
Member

Awesome @freddyaboulton! LGTM

@freddyaboulton
Copy link
Copy Markdown
Collaborator Author

Thanks for the review @abidlabs !

@freddyaboulton freddyaboulton merged commit 9f7dd05 into main Sep 23, 2022
@freddyaboulton freddyaboulton deleted the fix-queue-when-app-mounted branch September 23, 2022 20:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Cant mount gradio as a sub-application if the queue is enabled

3 participants