Skill Tracer
Skills › Scientific Agent Skills › modal › volumes.md

volumes.md

269 lines · skills/modal/references/volumes.md at 5f2bc03

Open on GitHub
1# Modal Volumes
2
3Reviewed against SDK 1.6.0 and the official [Volumes guide](https://modal.com/docs/guide/volumes).
4Examples with training/model helpers are illustrative; no cloud persistence test was run.
5

Modal Volumes

Reviewed against SDK 1.6.0 and the official Volumes guide. Examples with training/model helpers are illustrative; no cloud persistence test was run.

Raw lines 1 to 5
6## Table of Contents
7
8- [Overview](#overview)
9- [Creating Volumes](#creating-volumes)
10- [Mounting Volumes](#mounting-volumes)
11- [Reading and Writing Files](#reading-and-writing-files)
12- [CLI Access](#cli-access)
13- [Commits and Reloads](#commits-and-reloads)
14- [Concurrent Access](#concurrent-access)
15- [Volumes v2](#volumes-v2)
16- [Common Patterns](#common-patterns)

Table of Contents

  • Overview
  • Creating Volumes
  • Mounting Volumes
  • Reading and Writing Files
  • CLI Access
  • Commits and Reloads
  • Concurrent Access
  • Volumes v2
  • Common Patterns
Raw lines 6 to 17
18## Overview
20Volumes are Modal's distributed file system, optimized for write-once, read-many workloads like storing model weights and distributing them across containers.
22Key characteristics:
23- Persistent across function invocations and deployments
24- Mountable by multiple functions simultaneously
25- Background auto-commits every few seconds
26- Final commit on container shutdown

Overview

Volumes are Modal's distributed file system, optimized for write-once, read-many workloads like storing model weights and distributing them across containers.

Key characteristics:

  • Persistent across function invocations and deployments
  • Mountable by multiple functions simultaneously
  • Background auto-commits every few seconds
  • Final commit on container shutdown
Raw lines 18 to 27
28## Creating Volumes

Creating Volumes

Raw lines 28 to 29
30### In Code (Lazy Creation)
32```python
33vol = modal.Volume.from_name("my-volume", create_if_missing=True)
34```

In Code (Lazy Creation)

vol = modal.Volume.from_name("my-volume", create_if_missing=True)
Raw lines 30 to 35
36### Via CLI
38```bash
39modal volume create my-volume
41# v2 volume (beta)
42modal volume create my-volume --version=2
43```

Via CLI

modal volume create my-volume

# v2 volume (beta)
modal volume create my-volume --version=2
Raw lines 36 to 44
45### Programmatic v2
47```python
48vol = modal.Volume.from_name("my-volume", create_if_missing=True, version=2)
49```

Programmatic v2

vol = modal.Volume.from_name("my-volume", create_if_missing=True, version=2)
Raw lines 45 to 50
51## Mounting Volumes
53Mount volumes to functions via the `volumes` parameter:
55```python
56vol = modal.Volume.from_name("model-store", create_if_missing=True)
58@app.function(volumes={"/models": vol})
59def use_model():
60 import json
61 # Access files at /models/
62 with open("/models/config.json") as f:
63 config = json.load(f)
64```
66Mount multiple volumes:
68```python
69weights_vol = modal.Volume.from_name("weights")
70data_vol = modal.Volume.from_name("datasets")
72@app.function(volumes={"/weights": weights_vol, "/data": data_vol})
73def train():
74 ...
75```

Mounting Volumes

Mount volumes to functions via the volumes parameter:

vol = modal.Volume.from_name("model-store", create_if_missing=True)

@app.function(volumes={"/models": vol})
def use_model():
    import json
    # Access files at /models/
    with open("/models/config.json") as f:
        config = json.load(f)

Mount multiple volumes:

weights_vol = modal.Volume.from_name("weights")
data_vol = modal.Volume.from_name("datasets")

@app.function(volumes={"/weights": weights_vol, "/data": data_vol})
def train():
    ...
Raw lines 51 to 76
77## Reading and Writing Files

Reading and Writing Files

Raw lines 77 to 78
79### Writing
81```python
82@app.function(volumes={"/data": vol})
83def save_results(results):
84 import json
85 import os
87 os.makedirs("/data/outputs", exist_ok=True)
88 with open("/data/outputs/results.json", "w") as f:
89 json.dump(results, f)
90 vol.commit()
91```

Writing

@app.function(volumes={"/data": vol})
def save_results(results):
    import json
    import os

    os.makedirs("/data/outputs", exist_ok=True)
    with open("/data/outputs/results.json", "w") as f:
        json.dump(results, f)
    vol.commit()
Raw lines 79 to 92
93### Reading
95```python
96@app.function(volumes={"/data": vol})
97def load_results():
98 import json
99 vol.reload()
100 with open("/data/outputs/results.json") as f:
101 return json.load(f)
102```

Reading

@app.function(volumes={"/data": vol})
def load_results():
    import json
    vol.reload()
    with open("/data/outputs/results.json") as f:
        return json.load(f)
Raw lines 93 to 103
104### Large Files (Model Weights)
106```python
107@app.function(volumes={"/models": vol}, gpu="L40S")
108def save_model():
109 import torch
110 model = train_model()
111 torch.save(model.state_dict(), "/models/checkpoint.pt")
112 vol.commit()
114@app.function(volumes={"/models": vol}, gpu="L40S")
115def load_model():
116 import torch
117 vol.reload()
118 model = MyModel()
119 model.load_state_dict(torch.load("/models/checkpoint.pt", weights_only=True))
120 return model
121```

Large Files (Model Weights)

@app.function(volumes={"/models": vol}, gpu="L40S")
def save_model():
    import torch
    model = train_model()
    torch.save(model.state_dict(), "/models/checkpoint.pt")
    vol.commit()

@app.function(volumes={"/models": vol}, gpu="L40S")
def load_model():
    import torch
    vol.reload()
    model = MyModel()
    model.load_state_dict(torch.load("/models/checkpoint.pt", weights_only=True))
    return model
Raw lines 104 to 122
123## CLI Access
125```bash
126# List files
127modal volume ls my-volume
128modal volume ls my-volume /subdir/
130# Upload files
131modal volume put my-volume local_file.txt
132modal volume put my-volume local_file.txt /remote/path/file.txt
134# Download files
135modal volume get my-volume /remote/file.txt local_file.txt
137# Delete a volume
138modal volume delete my-volume
139```

CLI Access

# List files
modal volume ls my-volume
modal volume ls my-volume /subdir/

# Upload files
modal volume put my-volume local_file.txt
modal volume put my-volume local_file.txt /remote/path/file.txt

# Download files
modal volume get my-volume /remote/file.txt local_file.txt

# Delete a volume
modal volume delete my-volume
Raw lines 123 to 140
141## Commits and Reloads
143Modal auto-commits volume changes in the background every few seconds and on container shutdown.

Commits and Reloads

Modal auto-commits volume changes in the background every few seconds and on container shutdown.

Raw lines 141 to 144
145### Explicit Commit
147Persist the writer's changes before signaling downstream work. A commit does not
148refresh an already mounted reader; that reader must reload after closing its open
149volume file handles:
151```python
152@app.function(volumes={"/data": vol})
153def writer():
154 with open("/data/file.txt", "w") as f:
155 f.write("hello")
156 vol.commit() # Persist; already mounted readers still need reload()
157```

Explicit Commit

Persist the writer's changes before signaling downstream work. A commit does not refresh an already mounted reader; that reader must reload after closing its open volume file handles:

@app.function(volumes={"/data": vol})
def writer():
    with open("/data/file.txt", "w") as f:
        f.write("hello")
    vol.commit()  # Persist; already mounted readers still need reload()
Raw lines 145 to 158
159### Reload
161See changes from other containers:
163```python
164@app.function(volumes={"/data": vol})
165def reader():
166 vol.reload() # Refresh to see latest writes
167 with open("/data/file.txt") as f:
168 return f.read()
169```
171Close all file handles first, and do not read/write the mount while `reload()` runs:
172it temporarily appears empty to the reloading container. Reload from the mounted
173Function; a local SDK call does not refresh a remote container's mount.

Reload

See changes from other containers:

@app.function(volumes={"/data": vol})
def reader():
    vol.reload()  # Refresh to see latest writes
    with open("/data/file.txt") as f:
        return f.read()

Close all file handles first, and do not read/write the mount while reload() runs: it temporarily appears empty to the reloading container. Reload from the mounted Function; a local SDK call does not refresh a remote container's mount.

Raw lines 159 to 174
175## Concurrent Access

Concurrent Access

Raw lines 175 to 176
177### v1 Volumes
179- Recommended max 5 concurrent commits
180- Last write wins for concurrent modifications of the same file
181- Avoid concurrent modification of identical files
182- Max 500,000 files (inodes)

v1 Volumes

  • Recommended max 5 concurrent commits
  • Last write wins for concurrent modifications of the same file
  • Avoid concurrent modification of identical files
  • Max 500,000 files (inodes)
Raw lines 177 to 183
184### v2 Volumes
186- Hundreds of concurrent writers (distinct files)
187- No file count limit
188- Improved random access performance
189- Files strictly smaller than 1 TiB; at most 262,144 files per directory

v2 Volumes

  • Hundreds of concurrent writers (distinct files)
  • No file count limit
  • Improved random access performance
  • Files strictly smaller than 1 TiB; at most 262,144 files per directory
Raw lines 184 to 190
191## Volumes v2
193v2 Volumes (beta) offer significant improvements:
194The official guide does not yet guarantee against data loss. Retain another copy
195of irreplaceable research data; do not treat v2 as the sole durable archive.
197| Feature | v1 | v2 |
198|---------|----|----|
199| Max files | 500,000 | Unlimited |
200| Concurrent writes | ~5 | Hundreds |
201| Max file size | No documented limit | Less than 1 TiB |
202| Random access | Limited | Full support |
203| Hard links | No | Yes |
205Enable v2:
207```python
208vol = modal.Volume.from_name("my-vol-v2", create_if_missing=True, version=2)
209```

Volumes v2

v2 Volumes (beta) offer significant improvements: The official guide does not yet guarantee against data loss. Retain another copy of irreplaceable research data; do not treat v2 as the sole durable archive.

Featurev1v2
Max files500,000Unlimited
Concurrent writes~5Hundreds
Max file sizeNo documented limitLess than 1 TiB
Random accessLimitedFull support
Hard linksNoYes

Enable v2:

vol = modal.Volume.from_name("my-vol-v2", create_if_missing=True, version=2)
Raw lines 191 to 210
211## Common Patterns

Common Patterns

Raw lines 211 to 212
213### Model Weight Storage
215```python
216vol = modal.Volume.from_name("model-weights", create_if_missing=True)
218# Download once during image build
219def download_weights():
220 from huggingface_hub import snapshot_download
221 snapshot_download("openai-community/gpt2", local_dir="/models/gpt2")
222 vol.commit()
224image = (
225 modal.Image.debian_slim()
226 .uv_pip_install("huggingface_hub")
227 .run_function(download_weights, volumes={"/models": vol})
229```
231Mount the same Volume in consuming Functions. A cached `run_function` build step
232does not rerun if Volume files are later removed; use a dedicated download Function
233when population must be checked independently of the Image cache.

Model Weight Storage

vol = modal.Volume.from_name("model-weights", create_if_missing=True)

# Download once during image build
def download_weights():
    from huggingface_hub import snapshot_download
    snapshot_download("openai-community/gpt2", local_dir="/models/gpt2")
    vol.commit()

image = (
    modal.Image.debian_slim()
    .uv_pip_install("huggingface_hub")
    .run_function(download_weights, volumes={"/models": vol})
)

Mount the same Volume in consuming Functions. A cached run_function build step does not rerun if Volume files are later removed; use a dedicated download Function when population must be checked independently of the Image cache.

Raw lines 213 to 234
235### Training Checkpoints
237```python
238@app.function(volumes={"/checkpoints": vol}, gpu="H100", timeout=86400)
239def train():
240 for epoch in range(100):
241 train_one_epoch()
242 torch.save(model.state_dict(), f"/checkpoints/epoch_{epoch}.pt")
243 vol.commit() # Save checkpoint immediately
244```

Training Checkpoints

@app.function(volumes={"/checkpoints": vol}, gpu="H100", timeout=86400)
def train():
    for epoch in range(100):
        train_one_epoch()
        torch.save(model.state_dict(), f"/checkpoints/epoch_{epoch}.pt")
        vol.commit()  # Save checkpoint immediately
Raw lines 235 to 245
246### Shared Data Between Functions
248```python
249data_vol = modal.Volume.from_name("shared-data", create_if_missing=True)
251@app.function(volumes={"/data": data_vol})
252def preprocess():
253 # Write processed data
254 df.to_parquet("/data/processed.parquet")
255 data_vol.commit()
257@app.function(volumes={"/data": data_vol})
258def analyze():
259 data_vol.reload() # Ensure we see latest data
260 df = pd.read_parquet("/data/processed.parquet")
261 return df.describe()
262```

Shared Data Between Functions

data_vol = modal.Volume.from_name("shared-data", create_if_missing=True)

@app.function(volumes={"/data": data_vol})
def preprocess():
    # Write processed data
    df.to_parquet("/data/processed.parquet")
    data_vol.commit()

@app.function(volumes={"/data": data_vol})
def analyze():
    data_vol.reload()  # Ensure we see latest data
    df = pd.read_parquet("/data/processed.parquet")
    return df.describe()
Raw lines 246 to 263
264### Performance Tips
266- Volumes are optimized for large files, not many small files
267- Keep under 50,000 files and directories for best v1 performance
268- Use Parquet or other columnar formats instead of many small CSVs
269- For truly temporary data, use `ephemeral_disk` instead of Volumes

Performance Tips

  • Volumes are optimized for large files, not many small files
  • Keep under 50,000 files and directories for best v1 performance
  • Use Parquet or other columnar formats instead of many small CSVs
  • For truly temporary data, use ephemeral_disk instead of Volumes
Raw lines 264 to 269