# Learning Mojo GPU Programming Without a Local GPU

**URL:** <https://forum.modular.com/t/learning-mojo-gpu-programming-without-a-local-gpu/1637>\
**Category:** GPU Programming\
**Created:** [June 11, 2025, 10:37am UTC](https://forum.modular.com/t/learning-mojo-gpu-programming-without-a-local-gpu/1637 "2025-06-11T10:37:38Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![ficapy](https://avatars.discourse-cdn.com/v4/letter/f/96bed5/32.png) [@ficapy](https://forum.modular.com/u/ficapy)\
**Post date:** [June 11, 2025, 10:37am UTC](https://forum.modular.com/t/learning-mojo-gpu-programming-without-a-local-gpu/1637/1 "2025-06-11T10:37:38Z")

</div>

Hey everyone,

I’ve been really interested in learning GPU programming with Mojo, but I don’t have a GPU, and I’m not planning on buying an NVIDIA card anytime soon. So, I decided to try out a serverless GPU service to get some practice in.

My first thought was to use GCP, since I already use Google CloudRun services ([see their announcement here](https://cloud.google.com/blog/products/serverless/cloud-run-gpus-are-now-generally-available)). However, I quickly found out that I didn’t have a GPU quota by default and would need to request one. To get started faster, I decided to go with [RunPod.io](https://runpod.io) instead.

### The Basic Idea

The approach is pretty simple:

1. I send my Mojo code to a serverless endpoint.
2. The serverless function executes the code on a GPU and sends back the result.

### The Implementation

First, I created a new GitHub repository for the project.

Here’s the `Dockerfile`. A quick note: if you swap `max-core` with the full `modular` package, the Docker image size jumps from about 1.5GB to around 4GB.

```dockerfile
FROM ubuntu:24.04

WORKDIR /gpu-intro

RUN apt-get update && \
    apt-get install -y curl && \
    rm -rf /var/lib/apt/lists/* && \
    curl -fsSL https://pixi.sh/install.sh | sh && \
    /root/.pixi/bin/pixi init . \
      -c https://conda.modular.com/max-nightly/ -c conda-forge && \
    /root/.pixi/bin/pixi add max-core flask && \
    /root/.pixi/bin/pixi add --pypi runpod && \
    rm -rf /root/.cache

COPY server.py .

CMD ["/gpu-intro/.pixi/envs/default/bin/python", "server.py"]

```

And here’s the `server.py` file that runs on the serverless instance. It takes Base64 encoded files, writes them to disk, and executes `main.mojo`.

```python
import subprocess
import runpod
from base64 import b64decode

def handler(event):
    input_data = event["input"]
    
    if "main.mojo" not in input_data:
        return {"error": "main.mojo file not found in json"}

    try:
        # Decode and write all provided files to disk
        for filename, base64_content in input_data.items():
            file_content = b64decode(base64_content)
            with open(filename, 'wb') as f:
                f.write(file_content)
        
        # Run the mojo command
        result = subprocess.run(
            "/root/.pixi/bin/pixi run mojo main.mojo",
            shell=True,
            capture_output=True,
            text=True,
            check=True
        )
        return result.stdout
        
    except subprocess.CalledProcessError as e:
        error_msg = (
            f"Execution failed with code {e.returncode}:\n"
            f"STDOUT:\n{e.stdout}\n"
            f"STDERR:\n{e.stderr}"
        )
        return {"error": error_msg}
    except Exception as e:
        return {"error": str(e)}

# Start the serverless handler
runpod.serverless.start({"handler": handler})

```

Next, head over to [RunPod.io](http://RunPod.io) and create a new Serverless Endpoint. You’ll point it to your new GitHub repository, select some GPU that’s compatible with Mojo, and you can leave most of the other settings at their defaults. After a short wait, RunPod will automatically build your Docker image and deploy the endpoint.

### Calling the Endpoint

To call the endpoint, you can use a simple Python script like this. It reads your `main.mojo` file, encodes it, and sends it to the RunPod API.

`call.py`

```python
import requests
from base64 import b64encode

headers = {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer <your_api_key>'
}

FILENAMES = ["main.mojo"]

data = {
    'input': {i:b64encode(open(i,mode='rb').read()).decode('utf-8') for i in FILENAMES}
}

response = requests.post('https://api.runpod.ai/v2/<your_project_id>/runsync', headers=headers, json=data)
print(response.json()['output'])

```

### Final Thoughts

Just a heads-up, this is a very basic and barebones implementation, but it gets the job done!

**Cost:** As for the cost, it’s practically free for this kind of light use. A cold start takes about 10 seconds to respond, while a warm start is around 3 seconds. Since serverless billing is per-second, the cost is negligible for simple experiments.

Hope this helps anyone else out there who wants to tinker with Mojo on a GPU without the hardware
