Functions and Scaling Groups
Deploy a function, call it once and many times at once, retry calls that fail, and delete what you deployed.
A function is Python code that you deploy to Chalk and call from anywhere: a script, a notebook, or another function. In this tutorial you will:
You need:
deploy.create and
deploy.redeploy permissions. If you created your Chalk team yourself, you
already have them. Otherwise, ask an admin for a role that includes them. See
Permissions.You do not need a Chalk project or chalk init, and you do not deploy anything
with chalk apply. Every function in this tutorial is deployed by the tutorial
and deleted in the last step.
Set up the Python environment that will run the tutorial:
If you followed the Setup Guide, chalk init already
installed chalkcompute in your project. Run the tutorial’s files from your
project folder with uv run python.
Otherwise, install the Chalk CLI,
run chalk login, and install chalkcompute in a virtual environment:
python3 -m venv .venv
source .venv/bin/activate
pip install chalkcompute
On Windows, activate the environment with .venv\Scripts\activate instead.
Run the tutorial’s files with python, from the folder where you ran
chalk login, so that chalkcompute finds your login. See
Logins and environments.
Save this file as functions_tutorial.py:
import chalkcompute
@chalkcompute.function
def word_count(text: str) -> int:
return len(text.split())
if __name__ == "__main__":
print(word_count.remote("Chalk deploys this function and runs it"))
Run it with python functions_tutorial.py. While the function deploys, the
script prints its progress, and then the result:
7
Running the file deploys word_count. The first deploy can take a few minutes.
Later runs deploy again only if the function has changed. .remote(...) waits
until the deployed function is ready, calls it, and returns its result.
word_count("..."), without .remote, would run the Python body on your own
machine instead.
Chalk imports this file inside the deployed function, and that runs its
top-level code. Keep functions at the top level of the file, and put the code
that calls them under if __name__ == "__main__":, so that it runs only when
you run the file.
Suppose each call does slower work, such as calling a model. To process a list of texts quickly, start every call at once and collect the results afterward.
Replace the contents of functions_tutorial.py with:
import time
import chalkcompute
@chalkcompute.function(concurrency=10)
def word_count(text: str) -> int:
time.sleep(2) # Stands in for slower work, such as calling a model.
return len(text.split())
if __name__ == "__main__":
texts = [
"one",
"one two",
"one two three",
"one two three four",
"one two three four five",
"one two three four five six",
"one two three four five six seven",
"one two three four five six seven eight",
"one two three four five six seven eight nine",
"one two three four five six seven eight nine ten",
]
word_count.wait_ready() # Waits until the new version is deployed and ready.
start = time.time()
handles = [word_count.defer(text) for text in texts]
print(chalkcompute.gather(handles))
print(f"{time.time() - start:.0f} seconds")
Run it again:
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
4 seconds
Your time will differ a little. The changed function deploys as a new version,
and wait_ready() waits until that version can take calls, so the timing
covers only the calls.
.defer(...) sends a call to the function queue and returns a handle right
away, without waiting for the result. chalkcompute.gather(...) waits for all
the handles and returns their results in the same order as the calls.
concurrency=10 lets ten calls run at the same time. A function runs one call
at a time unless you set concurrency, so without it these ten calls would
run one after another, taking 20 seconds or more. See
Rate and Concurrency Limits.
Calls can fail for reasons that go away on their own, such as a network error
in a service the function calls. retries runs a failed call again.
To see a retry happen, the function below fails on its first two attempts for each request. It counts the attempts in a file inside the deployed function.
Replace the contents of functions_tutorial.py with:
import time
import uuid
import chalkcompute
@chalkcompute.function(concurrency=10)
def word_count(text: str) -> int:
time.sleep(2) # Stands in for slower work, such as calling a model.
return len(text.split())
@chalkcompute.function(retries=3)
def flaky_word_count(request_id: str, text: str) -> str:
import os
path = f"/tmp/attempts-{request_id}"
attempt = int(open(path).read()) + 1 if os.path.exists(path) else 1
with open(path, "w") as f:
f.write(str(attempt))
if attempt < 3:
raise ConnectionError(f"Attempt {attempt} failed")
return f"{len(text.split())} words, on attempt {attempt}"
if __name__ == "__main__":
flaky_word_count.wait_ready() # Waits until the new function is deployed and ready.
handle = flaky_word_count.defer(str(uuid.uuid4()), "This call fails twice before it works")
print(handle.get())
Run it again. This step takes a minute or two, because flaky_word_count is
deployed for the first time and each retry waits before it runs:
7 words, on attempt 3
retries=3 allows up to three more attempts after the first one fails. The
first two attempts raised ConnectionError, and the third returned a result.
Retries apply to calls made with .defer(...), which go through the function
queue. A call made with .remote(...) raises the first failure to the caller.
A deployed function keeps running until you delete it. Save this file as
cleanup.py and run it with python cleanup.py:
import chalkcompute
for name in ["word_count", "flaky_word_count"]:
chalkcompute.RemoteFunction.from_name(name).delete()
delete() removes each function and all of its versions.