Skip to content

Commit

Permalink
fixes webapp, updates text (#867)
Browse files Browse the repository at this point in the history
  • Loading branch information
charlesfrye authored Sep 9, 2024
1 parent e4cc765 commit 18d5b45
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 47 deletions.
52 changes: 27 additions & 25 deletions 09_job_queues/doc_ocr_jobs.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,22 @@
#
# This tutorial shows you how to use Modal as an infinitely scalable job queue
# that can service async tasks from a web app. For the purpose of this tutorial,
# we've also built a [React + FastAPI web app on Modal](/docs/examples/doc_ocr_webapp)
# we've also built a [React + FastAPI web app on Modal](https://modal.com/docs/examples/doc_ocr_webapp)
# that works together with it, but note that you don't need a web app running on Modal
# to use this pattern. You can submit async tasks to Modal from any Python
# application (for example, a regular Django app running on Kubernetes).
#
# Our job queue will handle a single task: running OCR transcription for images.
# We'll make use of a pre-trained Document Understanding model using the
# [donut](https://github.com/clovaai/donut) package to accomplish this. Try
# it out for yourself [here](https://modal-labs-example-doc-ocr-webapp-wrapper.modal.run/).
# [`donut`](https://github.com/clovaai/donut) package. Try
# it out for yourself [here](https://modal-labs--example-doc-ocr-webapp-wrapper.modal.run/).
#
# ![receipt parser frontend](./receipt_parser_frontend_2.jpg)

# ## Define an App
#
# Let's first import `modal` and define a [`App`](/docs/reference/modal.App). Later, we'll use the name provided
# for our `App` to find it from our web app, and submit tasks to it.
# Let's first import `modal` and define a [`App`](https://modal.com/docs/reference/modal.App).
# Later, we'll use the name provided for our `App` to find it from our web app, and submit tasks to it.

import urllib.request

Expand All @@ -33,17 +33,22 @@
#
# `donut` downloads the weights for pre-trained models to a local directory, if those weights don't already exist.
# To decrease start-up time, we want this download to happen just once, even across separate function invocations.
# To accomplish this, we use the [`Image.run_function`](/docs/reference/modal.Image#run_function) method, which allows
# us to run some code at image build time to save the model weights into the image.
# To accomplish this, we use the [`Image.run_function`](https://modal.com/docs/reference/modal.Image#run_function) method,
# which allows us to run some code at image build time to save the model weights into the image.

CACHE_PATH = "/root/model_cache"
MODEL_NAME = "naver-clova-ix/donut-base-finetuned-cord-v2"


def download_model_weights() -> None:
from huggingface_hub import snapshot_download
def get_model():
from donut import DonutModel

pretrained_model = DonutModel.from_pretrained(
MODEL_NAME,
cache_dir=CACHE_PATH,
)

snapshot_download(repo_id=MODEL_NAME, cache_dir=CACHE_PATH)
return pretrained_model


image = (
Expand All @@ -54,15 +59,15 @@ def download_model_weights() -> None:
"transformers==4.21.3",
"timm==0.5.4",
)
.run_function(download_model_weights)
.run_function(get_model)
)

# ## Handler function
#
# Now let's define our handler function. Using the [@app.function()](https://modal.com/docs/reference/modal.App#function)
# decorator, we set up a Modal [Function](/docs/reference/modal.Function) that uses GPUs,
# runs on a [custom container image](/docs/guide/custom-container),
# and automatically [retries](/docs/guide/retries#function-retries) failures up to 3 times.
# decorator, we set up a Modal [Function](https://modal.com/docs/reference/modal.Function) that uses GPUs,
# runs on a [custom container image](https://modal.com/docs/guide/custom-container),
# and automatically [retries](https://modal.com/docs/guide/retries#function-retries) failures up to 3 times.


@app.function(
Expand All @@ -74,15 +79,11 @@ def parse_receipt(image: bytes):
import io

import torch
from donut import DonutModel
from PIL import Image

# Use donut fine-tuned on an OCR dataset.
task_prompt = "<s_cord-v2>"
pretrained_model = DonutModel.from_pretrained(
MODEL_NAME,
cache_dir=CACHE_PATH,
)
pretrained_model = get_model()

# Initialize model.
pretrained_model.half()
Expand All @@ -107,8 +108,8 @@ def parse_receipt(image: bytes):
# modal deploy doc_ocr_jobs.py
# ```
#
# Once it's published, we can [look up](/docs/guide/trigger-deployed-functions) this function from another
# Python process and submit tasks to it:
# Once it's published, we can [look up](https://modal.com/docs/guide/trigger-deployed-functions) this function
# from another Python process and submit tasks to it:
#
# ```python
# fn = modal.Function.lookup("example-doc-ocr-jobs", "parse_receipt")
Expand All @@ -117,7 +118,7 @@ def parse_receipt(image: bytes):
#
# Modal will auto-scale to handle all the tasks queued, and
# then scale back down to 0 when there's no work left. To see how you could use this from a Python web
# app, take a look at the [receipt parser frontend](/docs/examples/doc_ocr_webapp)
# app, take a look at the [receipt parser frontend](https://modal.com/docs/examples/doc_ocr_webapp)
# tutorial.

# ## Run manually
Expand All @@ -136,8 +137,9 @@ def main():
if receipt_filename.exists():
with open(receipt_filename, "rb") as f:
image = f.read()
print(f"running OCR on {f.name}")
else:
image = urllib.request.urlopen(
"https://nwlc.org/wp-content/uploads/2022/01/Brandys-walmart-receipt-8.webp"
).read()
receipt_url = "https://nwlc.org/wp-content/uploads/2022/01/Brandys-walmart-receipt-8.webp"
image = urllib.request.urlopen(receipt_url).read()
print(f"running OCR on sample from URL {receipt_url}")
print(parse_receipt.remote(image))
45 changes: 23 additions & 22 deletions 09_job_queues/doc_ocr_webapp.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
# ---
# deploy: true
# lambda-test: false
# cmd: ["modal", "serve", "09_job_queues/doc_ocr_webapp.py"]
# ---
#
# # Document OCR web app
#
# This tutorial shows you how to use Modal to deploy a fully serverless
# [React](https://reactjs.org/) + [FastAPI](https://fastapi.tiangolo.com/) application.
# We're going to build a simple "Receipt Parser" web app that submits OCR transcription
# tasks to a separate Modal app defined in the [Job Queue
# tutorial](/docs/examples/doc_ocr_jobs), polls until the task is completed, and displays
# tasks to a separate Modal app defined in the [Job Queue tutorial](https://modal.com/docs/examples/doc_ocr_jobs),
# polls until the task is completed, and displays
# the results. Try it out for yourself
# [here](https://modal-labs--example-doc-ocr-webapp-wrapper.modal.run/).
#
# ![receipt parser frontend](./receipt_parser_frontend.jpg)

# ## Basic setup
#
# Let's get the imports out of the way and define a [`App`](/docs/reference/modal.App).
# Let's get the imports out of the way and define a [`App`](https://modal.com/docs/reference/modal.App).

from pathlib import Path

Expand All @@ -27,8 +27,8 @@

app = modal.App("example-doc-ocr-webapp")

# Modal works with any [ASGI](/docs/guide/webhooks#serving-asgi-and-wsgi-apps) or
# [WSGI](/docs/guide/webhooks#wsgi) web framework. Here, we choose to use [FastAPI](https://fastapi.tiangolo.com/).
# Modal works with any [ASGI](https://modal.com/docs/guide/webhooks#serving-asgi-and-wsgi-apps) or
# [WSGI](https://modal.com/docs/guide/webhooks#wsgi) web framework. Here, we choose to use [FastAPI](https://fastapi.tiangolo.com/).

web_app = fastapi.FastAPI()

Expand All @@ -38,12 +38,13 @@
# and another to poll for the results of the job.
#
# In `parse`, we're going to submit tasks to the function defined in the [Job
# Queue tutorial](/docs/examples/doc_ocr_jobs), so we import it first using
# [`Function.lookup`](/docs/reference/modal.Function#lookup).
# Queue tutorial](https://modal.com/docs/examples/doc_ocr_jobs), so we import it first using
# [`Function.lookup`](https://modal.com/docs/reference/modal.Function#lookup).
#
# We call [`.spawn()`](/docs/reference/modal.Function#spawn) on the function handle
# we imported above, to kick off our function without blocking on the results. `spawn` returns
# a unique ID for the function call, that we can use later to poll for its result.
# We call [`.spawn()`](https://modal.com/docs/reference/modal.Function#spawn) on the function handle
# we imported above to kick off our function without blocking on the results. `spawn` returns
# a unique ID for the function call, which we then use
# to poll for its result.


@web_app.post("/parse")
Expand All @@ -65,7 +66,7 @@ async def parse(request: fastapi.Request):

@web_app.get("/result/{call_id}")
async def poll_results(call_id: str):
function_call = modal.FunctionCall.from_id(call_id)
function_call = modal.functions.FunctionCall.from_id(call_id)
try:
result = function_call.get(timeout=0)
except TimeoutError:
Expand All @@ -77,7 +78,7 @@ async def poll_results(call_id: str):
# Finally, we mount the static files for our front-end. We've made [a simple React
# app](https://github.com/modal-labs/modal-examples/tree/main/09_job_queues/doc_ocr_frontend)
# that hits the two endpoints defined above. To package these files with our app, first
# we get the local assets path, and then create a modal [`Mount`](/docs/guide/local-data#mounting-directories)
# we get the local assets path, and then create a modal [`Mount`](https://modal.com/docs/guide/local-data#mounting-directories)
# that mounts this directory at `/assets` inside our container. Then, we instruct FastAPI to [serve
# this static file directory](https://fastapi.tiangolo.com/tutorial/static-files/) at our root path.

Expand All @@ -98,27 +99,27 @@ def wrapper():

# ## Running
#
# You can run this as an ephemeral app, by running the command
# While developing, you can run this as an ephemeral app by executing the command
#
# ```shell
# modal serve doc_ocr_webapp.py
# ```
#
# Modal watches all the mounted files and updates the app if anything changes.
# See [these docs](https://www.notion.so/modal-com/Simple-RAG-chat-with-PDF-app-a20fc171f0fc415cb320606f737e6db4?pvs=4)
# for more details.
#
# ## Deploy
#
# That's all! To deploy your application, run
# To deploy your application, run
#
# ```shell
# modal deploy doc_ocr_webapp.py
# ```
#
# If successful, this will print a URL for your app, that you can navigate to from
# That's all!
#
# If successful, this will print a URL for your app that you can navigate to in
# your browser 🎉 .
#
# ![receipt parser processed](./receipt_parser_frontend_2.jpg)
#
# ## Developing
#
# If desired, instead of deploying, we can [serve](/docs/guide/webhooks#developing-with-modal-serve)
# our app ephemerally. In this case, Modal watches all the mounted files, and updates
# the app if anything changes.

0 comments on commit 18d5b45

Please sign in to comment.