Beam Cloud

https://www.beam.cloud/

Quick Start

Create a env for test:

$ python3 -m venv beam-quickstart/.venv
$ . beam-quickstart/.venv/bin/activate

Install Beam Client:

$ pip install beam-client
$ beam --version
beam, version 0.2.161

Configure:

$ beam configure default --token xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Added new context to /Users/xxxx/.beam/config.ini
$ cat /Users/xxxx/.beam/config.ini 
[default]
token = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
gateway_host = gateway.beam.cloud
gateway_port = 443

Show available machine types:

$ beam machine list
                        
  GPU Type   Available  
 ────────────────────── 
  A100-40       ✅      
  A100-80       ❌      
  A10G          ✅      
  A6000         ❌      
  H100          ✅      
  L4            ❌      
  L40S          ❌      
  RTX4090       ✅      
  T4            ✅      
                        
  9 items               

Create a Python app:

from beam import function


@function()
def square(x):
    print("This code is running on a remote worker!")
    return x**2


def main():
    print("The square is", square.remote(42))


if __name__ == "__main__":
    main()

Save it as app/app.py, then sumbit it to beam.cloud:

$ cd app
$ python app.py
=> Building image 
=> Using cached image 
=> Syncing files 

=> Uploading 
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 336/336 bytes 0:00:00
=> Files synced 
=> Running function: <app:square> 
Loading image <d055bc4ee4ad0e61>...
Loaded image <d055bc4ee4ad0e61>, took: 14.627716ms
This code is running on a remote worker!
=> Function complete <73aee64e-a7a0-4dc0-8c3e-176031b79566> 
The square is 1764

Tip

Beam Client will submit all .py files in the current working directory, so better make a new directory for a new app.

Create another Python app to check GPU:

from beam import function
import subprocess


@function(gpu="T4")
def is_gpu_available():
    print(subprocess.check_output(["nvidia-smi"]).decode())
    print("This code is running on a remote GPU!")


if __name__ == "__main__":
    is_gpu_available.remote()

Save it as gpu/gpu.py, then submit it:

$ cd gpu
$ python gpu.py 
=> Building image 
=> Using cached image 
=> Syncing files 

=> Uploading 
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 370/370 bytes 0:00:00
=> Files synced 
=> Running function: <gpu:is_gpu_available> 
Loading image <d055bc4ee4ad0e61>...
Loaded image <d055bc4ee4ad0e61>, took: 13.300503ms
Tue Jun  3 03:39:54 2025       
+-----------------------------------------------------------------------------------------+
| NVIDIA-SMI 570.86.15              Driver Version: 570.86.15      CUDA Version: 12.8     |
|-----------------------------------------+------------------------+----------------------+
| GPU  Name                 Persistence-M | Bus-Id          Disp.A | Volatile Uncorr. ECC |
| Fan  Temp   Perf          Pwr:Usage/Cap |           Memory-Usage | GPU-Util  Compute M. |
|                                         |                        |               MIG M. |
|=========================================+========================+======================|
|   0  Tesla T4                       On  |   00000000:00:1D.0 Off |                    0 |
| N/A   25C    P8             13W /   70W |       1MiB /  15360MiB |      0%      Default |
|                                         |                        |                  N/A |
+-----------------------------------------+------------------------+----------------------+
                                                                                         
+-----------------------------------------------------------------------------------------+
| Processes:                                                                              |
|  GPU   GI   CI              PID   Type   Process name                        GPU Memory |
|        ID   ID                                                               Usage      |
|=========================================================================================|
|  No running processes found                                                             |
+-----------------------------------------------------------------------------------------+
This code is running on a remote GPU!
=> Function complete <428c8442-dd62-46ca-9adf-23b364c08f4a>

Deploy Beta9

Deploy local-path StorageClass

$ curl -LO https://raw.githubusercontent.com/rancher/local-path-provisioner/v0.0.31/deploy/local-path-storage.yaml
$ kubectl apply -f local-path-storage.yaml 
namespace/local-path-storage created
serviceaccount/local-path-provisioner-service-account created
role.rbac.authorization.k8s.io/local-path-provisioner-role created
clusterrole.rbac.authorization.k8s.io/local-path-provisioner-role created
rolebinding.rbac.authorization.k8s.io/local-path-provisioner-bind created
clusterrolebinding.rbac.authorization.k8s.io/local-path-provisioner-bind created
deployment.apps/local-path-provisioner created
storageclass.storage.k8s.io/local-path created
configmap/local-path-config created

Check existence:

$ kubectl get sc local-path
NAME         PROVISIONER             RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
local-path   rancher.io/local-path   Delete          WaitForFirstConsumer   false                  2m5s

Create namespace beta9

$ kubectl create namespace beta9
namespace/beta9 created

Note

Why this is needed? For the workers need to create in namespace beta9, which seems to be hard coded. Then the redis must be in the same namespace, and the localstack.

Deploy localstack

Add repo and pull the chart:

$ helm repo add localstack-repo https://helm.localstack.cloud
$ helm pull localstack-repo/localstack

Create a file localstack_values.yaml to customize it:

extraEnvVars:
- name: SERVICES
  value: "s3"
enableStartupScripts: true
startupScriptContent: |
  #!/bin/bash
  awslocal s3 mb s3://juicefs
  awslocal s3 mb s3://logs
persistence:
  enabled: true
  storageClass: local-path
  accessModes:
  - ReadWriteOnce
  size: 50Gi

Install:

$ helm install localstack localstack-0.6.24.tgz -f localstack_values.yaml -n beta9
NAME: localstack
LAST DEPLOYED: Thu Jun 12 15:57:36 2025
NAMESPACE: beta9
STATUS: deployed
REVISION: 1
NOTES:
1. Get the application URL by running these commands:
  export NODE_PORT=$(kubectl get --namespace "beta9" -o jsonpath="{.spec.ports[0].nodePort}" services localstack)
  export NODE_IP=$(kubectl get nodes --namespace "beta9" -o jsonpath="{.items[0].status.addresses[0].address}")
  echo http://$NODE_IP:$NODE_PORT

Deploy beta9

$ helm pull oci://public.ecr.aws/n4e0e1y0/beta9-chart
Pulled: public.ecr.aws/n4e0e1y0/beta9-chart:0.1.492
Digest: sha256:9c576c52acdfa6d80073252a157eb7cbc909df17ab0505e4926ceecdc66aeeb2
$ helm install beta9 beta9-chart-0.1.492.tgz -n beta9
NAME: beta9
LAST DEPLOYED: Thu Jun 12 15:47:43 2025
NAMESPACE: beta9
STATUS: deployed
REVISION: 1
TEST SUITE: None

Expose the service:

$ kubectl port-forward -n beta9 --address=0.0.0.0 svc/beta9-gateway 1993 1994
Forwarding from 0.0.0.0:1993 -> 1993
Forwarding from 0.0.0.0:1994 -> 1994

Install beta9 client

$ pip install beta9
$ beta9 --version
beta9, version 0.1.194

At the first time you run beta9, a default config is created:

$ beta9
=> Welcome to Beta9! Let's get started 📡 

           ,#@@&&&&&&&&&@&/
        @&&&&&&&&&&&&&&&&&&&&@#
         *@&&&&&&&&&&&&&&&&&&&&&@/
   ##      /&&&&&&&&&&&&&@&&&&&&&&@,
  @&&&&&.    (&&&&&&@/    &&&&&&&&&&/
 &&&&&&&&&@*   %&@.      @& ,@&&&&&&&,
.@&&&&&&&&&&&&#        &&*  ,@&&&&&&&&
*&&&&&&&&&&&@,   %&@/@&*    @&&&&&&&&@
.@&&&&&&&&&*      *&@     .@&&&&&&&&&&
 %&&&&&&&&     /@@*     .@&&&&&&&&&&@,
  &&&&&&&/.#@&&.     .&&&    %&&&&&@,
   /&&&&&&&@%*,,*#@&&(         ,@&&
     /&&&&&&&&&&&&&&,
        #@&&&&&&&&&&,
            ,(&@@&&&,

Gateway Host [0.0.0.0]: 10.220.70.56
Gateway Port [1993]: 
Token: 
=> Authorizing with gateway 
=> Authorized 🎉 
Usage: beta9 [OPTIONS] COMMAND [ARGS]...

Options:
  -c, --context TEXT  The config context to use.  [default: default]
  --version           Show the version and exit.
  -h, --help          Show this message and exit.

Common Commands:
  deploy  Deploy a new function.
  serve   Serve a function.
  ls      List contents in a volume.
  cp      Upload or download contents to or from a volume.
  rm      Remove content from a volume.
  mv      Move a file or directory to a new location within the same volume.
  shell   Connect to a container with the same config as your handler.
  run     Run a container.
  dev     Spins up a remote environment to develop in.

Management Commands:
  config      Manage configuration contexts.
  container   Manage containers.
  deployment  Manage deployments.
  machine     Manage remote machines.
  pool        Manage worker pools.
  secret      Manage secrets
  task        Manage tasks.
  token       Manage tokens.
  volume      Manage volumes.
  worker      Manage workers.

Note

The gateway address and port are vital, but the token can be left empty.

Show the configs:

$ cat ~/.beta9/config.ini 
[default]
token = nyIkEBkBTj0kWh4Ln1Aj5m4EeXUySxHcbjNoo98MgSTFEju7zaGKKVOrvvz2PHdCL7bi0rrLtin-df_O5Le8Uw==
gateway_host = 10.220.70.56
gateway_port = 1993

Run a program

Modify the app/app.py to use beta9:

-from beam import function
+from beta9 import function
 
 
 @function()

Run it:

$ python app.py 
=> Building image 

Loaded image <39b588f7692f2114>, took: 320ns
Python 3.10.12

Saving image, this may take a few minutes...
=> Build complete 🎉 
=> Syncing files 

=> Uploading 
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 336/336 bytes 0:00:00
=> Files synced 
=> Running function: <app:square> 
Loading image <88d1fd193ae6c25f>...
Loaded image <88d1fd193ae6c25f>, took: 269ns
Unable to connect to gateway.
Function failed <ae5cc75b-59fb-49b0-9883-74fbb8cba613> ❌
The square is None

Error

Error occured. Don’t know why.

Tip

Copy the beta9 config to beam:

$ cp ~/.beta9/config.ini ~/.beam/config.ini

Then you can run the program using beam. The output is just the same.