fait en sorte que a la fin je puisse sauvegardé mon modéle :

Modal logo
Examples
Guide
Reference
Dashboard
Introduction
Custom container images
Custom containers
Private registries
GPUs and other resources
GPU acceleration
Reserving CPU and memory
Scaling out
Scaling out
Dicts and queues
Job processing
Concurrent inputs on a single container (beta)
Secrets and environment variables
Secrets
Environment variables
Deployment
Managing deployments
Invoke deployed functions
Continuous deployment
Scheduling and cron jobs
Web endpoints
Web endpoints
Streaming endpoints
Web endpoint URLs
Request timeouts
Tunnels (beta)
Data sharing and storage
Passing local data
Volumes
Mounting local files and directories
Storing model weights
Cloud bucket mounts (Beta)
Network file systems (superseded)
Dynamic sandboxes
Performance
Cold start performance
Memory Snapshot (beta)
Geographic latency
Reliability and robustness
Failures and retries
Preemption
Timeouts
Troubleshooting
Security and privacy
Integrations
Connecting Modal to your Vercel account
Okta SSO
Other topics
File and project structure
Developing and debugging
Modal user account setup
Workspaces
Environments
Jupyter notebooks
Asynchronous API usage
Global variables
Container lifecycle and parameters
Apps, Stubs, and entrypoints
Volumes
The modal.Volume is a mutable volume built for high-performance file serving. Like the modal.NetworkFileSystem, these volumes can be simultaneously attached to multiple Modal functions, supporting concurrent reading and writing. But unlike the modal.NetworkFileSystem, the modal.Volume has been designed for fast reads and does not automatically synchronize writes between mounted volumes.

The modal.Volume works best with write-once, read-many I/O workloads.

Volumes work best when they contain less then 50,000 files and directories. The latency to attach or modify a volume scales linearly with the number of files in the volume, and past a few tens of thousands of files the linear component starts to dominate the fixed overhead.

There is currently a hard limit of 500,000 inodes (files, directories and symbolic links) per volume. If you reach this limit, any further attempts to create new files or directories will error with ENOSPC (No space left on device).

Creating a volume
The easiest way to create a Volume and use it as a part of your app is to use the modal volume create CLI command. This will create the volume and output some sample code:

% modal volume create my-test-volume
Created volume 'my-test-volume' in environment 'main'.

Copy
Using the volume from code
To attach a volume to a function, use Volume.from_name:

from modal import App, Volume

app = App()  # Note: prior to April 2024, "app" was called "stub"

vol = Volume.from_name("my-volume")

@app.function(volumes={"/data": vol})
def run():
    with open("/data/xyz.txt", "w") as f:
        f.write("hello")
    vol.commit()  # Needed to make sure all changes are persisted

Copy
Creating volumes lazily
You can also create volumes lazily from code using:

from modal import Volume

vol = Volume.from_name("my-volume", create_if_missing=True)

Copy
This will create the volume if it doesn’t exist.

Using the volume from the command line
You can also interact with volumes using the command line interface. You can run modal volume to get a full list of its subcommands:

% modal volume

 Usage: modal volume [OPTIONS] COMMAND [ARGS]...

 Read and edit modal.Volume volumes.
 Note: users of modal.NetworkFileSystem should use the modal nfs command instead.

╭─ Options ─────────────────────────────────────────────────────────────────────────────────────────────╮
│ --help          Show this message and exit.                                                           │
╰───────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭─ Commands ────────────────────────────────────────────────────────────────────────────────────────────╮
│ cp      Copy source file to destination file or multiple source files to destination directory.       │
│ create  Create a named, persistent modal.Volume.                                                      │
│ delete  Delete a named, persistent modal.Volume.                                                      │
│ get     Download files from a modal.Volume.                                                           │
│ list    List the details of all modal.Volume volumes in an environment.                               │
│ ls      List files and directories in a modal.Volume volume.                                          │
│ put     Upload a file or directory to a volume.                                                       │
│ rm      Delete a file or directory from a volume.                                                     │
╰───────────────────────────────────────────────────────────────────────────────────────────────────────╯

Copy
Volume commits and reloads
Unlike a networked filesystem, you need to explicitly reload the Volume to see changes made since it was first mounted. This reload is handled by invoking the .reload() method on a Volume object. Similarly, any volume changes made within a container need to be committed for those the changes to become visible outside the current container. This is handled by invoking the .commit() method on a Volume object, or by enabling background commits.

At container creation time the latest state of an attached Volume is mounted. If the Volume is then subsequently modified by a commit operation in another running container, that Volume modification won’t become available until the original container does a .reload().

Consider this example which demonstrates the effect of a reload:

import pathlib
import modal

app = modal.App()  # Note: prior to April 2024, "app" was called "stub"

volume = modal.Volume.from_name("my-volume")

p = pathlib.Path("/root/foo/bar.txt")


@app.function(volumes={"/root/foo": volume})
def f():
    p.write_text("hello")
    print(f"Created {p=}")
    volume.commit()  # Persist changes
    print(f"Committed {p=}")


@app.function(volumes={"/root/foo": volume})
def g(reload: bool = False):
    if reload:
        volume.reload()  # Fetch latest changes
    if p.exists():
        print(f"{p=} contains '{p.read_text()}'")
    else:
        print(f"{p=} does not exist!")


@app.local_entrypoint()
def main():
    g.remote()  # 1. container for `g` starts
    f.remote()  # 2. container for `f` starts, commits file
    g.remote(reload=False)  # 3. reuses container for `g`, no reload
    g.remote(reload=True)   # 4. reuses container, but reloads to see file.

Copy
The output for this example is this:

p=PosixPath('/root/foo/bar.txt') does not exist!
Created p=PosixPath('/root/foo/bar.txt')
Committed p=PosixPath('/root/foo/bar.txt')
p=PosixPath('/root/foo/bar.txt') does not exist!
p=PosixPath('/root/foo/bar.txt') contains hello

Copy
This code runs two containers, one for f and one for g. Only the last function invocation reads the file created and committed by f because it was configured to reload.

Background commits
Volumes have support for background committing that is in beta. This functionality periodically commits the state of your Volume so that your application code does not need to invoke .commit().

This functionality is enabled using the _allow_background_volume_commits flag on @app.function.

@app.function(volumes={"/vol/models": volume}, _allow_background_volume_commits=True)
def train():
    p = pathlib.Path("/vol/models/dummy.txt")
    p.write_text("I will be persisted without volume.commit()!")
    ...

Copy
During the execution of the train function shown above, every few seconds the attached Volume will be snapshotted and its new changes committed. A final snapshot and commit is also automatically performed on container shutdown.

Being able to persist changes to Volumes without changing your application code is especially useful when training or fine-tuning models.

Model serving
A single ML model can be served by simply baking it into a modal.Image at build time using run_function. But if you have dozens of models to serve, or otherwise need to decouple image builds from model storage and serving, use a modal.Volume.

Volumes can be used to save a large number of ML models and later serve any one of them at runtime with much better performance than can be achieved with a modal.NetworkFileSystem.

This snippet below shows the basic structure of the solution.

import modal

app = modal.App()  # Note: prior to April 2024, "app" was called "stub"
volume = modal.Volume.from_name("model-store")
model_store_path = "/vol/models"


@app.function(volumes={model_store_path: volume}, gpu="any")
def run_training():
    model = train(...)
    save(model_store_path, model)
    volume.commit()  # Persist changes


@app.function(volumes={model_store_path: volume})
def inference(model_id: str, request):
    try:
        model = load_model(model_store_path, model_id)
    except NotFound:
        volume.reload()  # Fetch latest changes
        model = load_model(model_store_path, model_id)
    return model.run(request)

Copy
Model checkpointing
Checkpoints are snapshots of an ML model and can be configured by the callback functions of ML frameworks. You can use saved checkpoints to restart a training job from the last saved checkpoint. This is particularly helpful in managing preemption.

Huggingface transformers
To periodically checkpoint into a modal.Volume, you must:

Enable background commits
Set the Trainer’s output_dir to write into the volume’s mount location.
import pathlib

VOL_MOUNT_PATH = pathlib.Path("/vol")


@app.function(
    gpu="A10g",
    timeout=7_200,
    volumes={VOL_MOUNT_PATH: volume},
)
def finetune():
    from transformers import Seq2SeqTrainer
    ...

    training_args = Seq2SeqTrainingArguments(
        output_dir=str(VOL_MOUNT_PATH / "model"),
        ...
    )

    trainer = Seq2SeqTrainer(
        model=model,
        args=training_args,
        train_dataset=tokenized_xsum_train,
        eval_dataset=tokenized_xsum_test,
    )

Copy
Filesystem consistency
Concurrent modification
Concurrent modification from multiple containers is supported, but concurrent modifications of the same files should be avoided. Last write wins in case of concurrent modification of the same file — any data the last writer didn’t have when committing changes will be lost!

The number of commits you can run concurrently is limited. If you run too many concurrent commits each commit will take longer due to contention. If you are committing small changes, avoid doing more than 5 concurrent commits (the number of concurrent commits you can make is proportional to the size of the changes being committed).

As a result, volumes are typically not a good fit for use cases where you need to make concurrent modifications to the same file (nor is distributed file locking supported).

While a commit or reload is in progress the volume will appear empty to the container that initiated the commit. That means you cannot read from or write to a volume in a container where a commit or reload is ongoing (note that this only applies to the container where the commit or reload was issued, other containers remain unaffected).

For example, this is not going to work:

volume = modal.Volume.from_name("my-volume")


@app.function(image=modal.Image.debian_slim().pip_install("aiofiles"), volumes={"/vol": volume})
async def concurrent_write_and_commit():
    async with aiofiles.open("/vol/big.file", "w") as f:
        await f.write("hello" * 1024 * 1024 * 500)

    async def f():
        await asyncio.sleep(0.1)  # Wait for the commit to start
        # This is going to fail with:
        # PermissionError: [Errno 1] Operation not permitted: '/vol/other.file'
        # since the commit is in progress when we attempt the write.
        async with aiofiles.open("/vol/other.file", "w") as f:
            await f.write("hello")

    await asyncio.gather(volume.commit.aio(), f())

Copy
Busy volume errors
You can only reload a volume when there no open files on the volume. If you have open files on the volume the .reload() operation will fail with “volume busy”. The following is a simple example of how a “volume busy” error can occur:

volume = modal.Volume.from_name("my-volume")


@app.function(volumes={"/vol": volume})
def reload_with_open_files():
    f = open("/vol/data.txt", "r")
    volume.reload()  # Cannot reload when files in the volume are open.

Copy
Can’t find file on volume errors
When accessing files in your volume, don’t forget to pre-pend where your Volume is mounted in the container.

In the example below, where the volume has been mounted at /data, “hello” is being written to /data/xyz.txt.

from modal import App, Volume

app = App()  # Note: prior to April 2024, "app" was called "stub"

vol = Volume.from_name("my-volume")

@app.function(volumes={"/data": vol}, _allow_background_volume_commits=True)
def run():
    with open("/data/xyz.txt", "w") as f:
        f.write("hello")
    vol.commit()

Copy
If you instead write to “/xyz.txt”, when you dump the contents of the volume, you will not see the xyz.txt file.

Further examples
Pet Art Dreambooth with Hugging Face and Gradio uses a volume for model storage
Volumes
Creating a volume
Using the volume from code
Creating volumes lazily
Using the volume from the command line
Volume commits and reloads
Background commits
Model serving
Model checkpointing
Huggingface transformers
Filesystem consistency
Concurrent modification
Busy volume errors
Can’t find file on volume errors
Further examples
See it in action
Fine-tuning and serving custom LLaMA models 
Modal logo
© 2024
About
Status
Changelog
Documentation
Slack Community
Pricing
Examples
Terms
Privacy
import time
import modal
from modal import App, Image, NetworkFileSystem, wsgi_app,Volume

vol = Volume.from_name("my-volume", create_if_missing=True)


dockerhub_image = Image.from_registry(
    "tensorflow/tensorflow:2.12.0-gpu",
).pip_install("protobuf==3.20.*","datasets","tensorflow_text",'numpy',"tensorflow_datasets")

app = App(
    "example-tensorflow-tutorial", image=dockerhub_image
)  # Note: prior to April 2024, "app" was called "stub"
vol = Volume.from_name("my-volume")

@app.function(gpu="A10G",volumes={"/data": vol})



def download():
    import datasets
    from datasets import load_dataset
    import tensorflow_text as tf_text
    import tensorflow_datasets as tfds
    import numpy as np
    import tensorflow as tf

    from datasets import load_dataset
    dataset = load_dataset("Helsinki-NLP/opus-100", "en-fr")
    data = dataset["train"]
    val = dataset["validation"]

    french_sentences = [example["fr"] for example in data["translation"]]
    english_sentences = [example["en"] for example in data["translation"]]
    vfrench_sentences = [example["fr"] for example in val["translation"]]
    venglish_sentences = [example["en"] for example in val["translation"]]
    dataset = tf.data.Dataset.from_tensor_slices((french_sentences, english_sentences))
    vdataset = tf.data.Dataset.from_tensor_slices((vfrench_sentences, venglish_sentences))

    french_sentences_decoded = []
    english_sentences_decoded = []

    vfrench_sentences_decoded = []
    venglish_sentences_decoded = []

    for french_sentence, english_sentence in dataset.take(len(french_sentences)):
        french_sentences_decoded.append("b '"+french_sentence.numpy().decode('utf-8'))
        english_sentences_decoded.append("b '"+english_sentence.numpy().decode('utf-8'))


    for french_sentence, english_sentence in dataset.take(len(vfrench_sentences)):
        vfrench_sentences_decoded.append("b '"+french_sentence.numpy().decode('utf-8'))
        venglish_sentences_decoded.append("b '"+english_sentence.numpy().decode('utf-8'))


    print("Nombre de phrases en français :", len(french_sentences_decoded))
    print("Nombre de phrases en anglais :", len(english_sentences_decoded))
    print("Nombre de phrases en français pour la validation :", len(vfrench_sentences_decoded))
    print("Nombre de phrases en anglais pour la validation :", len(venglish_sentences_decoded))
    

    train_fr = french_sentences
    train_en = english_sentences
    

    from tensorflow_text.tools.wordpiece_vocab import bert_vocab_from_dataset as bert_vocab
    print("YES")
    bert_tokenizer_params = dict(lower_case=False)
    reserved_tokens = ["[START]","[END]","[PAD]","[UNK]"]

    bert_vocab_args = {
        'vocab_size': 8000,
        'reserved_tokens': reserved_tokens,
        'bert_tokenizer_params': bert_tokenizer_params,
        'learn_params': {},
    }
    print("YES")
    from tensorflow_text.tools.wordpiece_vocab import bert_vocab_from_dataset as bert_vocab
    en_vocab = bert_vocab.bert_vocab_from_dataset(
        tf.data.Dataset.from_tensor_slices(english_sentences).batch(64).prefetch(2),
        **bert_vocab_args
    )
    print("fin de en VOCAB")
    fr_vocab = bert_vocab.bert_vocab_from_dataset(
            tf.data.Dataset.from_tensor_slices(french_sentences).batch(64).prefetch(2),
            **bert_vocab_args
        )
    
    def write_vocab_file(filepath, vocab):
        with open(filepath, 'w', encoding='utf-8') as f:
            for token in vocab:
                print(token, file=f)
    print("fin de FR VOCAB")
    write_vocab_file(('fr_vocab.txt', fr_vocab))
    write_vocab_file(('en_vocab.txt', en_vocab))
    fr_tokenizer = tf_text.BertTokenizer('fr_vocab.txt', **bert_tokenizer_params)
    en_tokenizer = tf_text.BertTokenizer('en_vocab.txt', **bert_tokenizer_params)
    

    max_length = 200
    inputs = tf.keras.Input(shape=(1,200))
    lstm_units = 512
    vocab_size_en = len(en_vocab) + len(reserved_tokens)
    vocab_size_fr = len(fr_vocab) + len(reserved_tokens)
    embedding_dim = 512
    print(vocab_size_en,vocab_size_fr)

    encoder_inputs = tf.keras.layers.Input(shape=(200,))
    encoder_embedding = tf.keras.layers.Embedding(input_dim=vocab_size_fr, output_dim=embedding_dim, mask_zero=True)(encoder_inputs)
    encoder_outputs, state_h, state_c = tf.keras.layers.LSTM(lstm_units, return_state=True)(encoder_embedding)
    encoder_states = [state_h, state_c]

    decoder_inputs = tf.keras.layers.Input(shape=(200,))
    decoder_embedding = tf.keras.layers.Embedding(input_dim=vocab_size_fr, output_dim=embedding_dim, mask_zero=True)(decoder_inputs)
    decoder_lstm = tf.keras.layers.LSTM(lstm_units, return_sequences=True, return_state=True)
    decoder_outputs, _, _ = decoder_lstm(decoder_embedding, initial_state=encoder_states)
    decoder_dense = tf.keras.layers.Dense(vocab_size_en, activation='Softmax')
    decoder_outputs = decoder_dense(decoder_outputs)
    model = tf.keras.models.Model(inputs=[encoder_inputs, decoder_inputs], outputs=decoder_outputs)
    model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'])


    model.summary()
    def format_sequences(tokenizer, sentences, max_length):
        sequences = tokenizer.tokenize(sentences).merge_dims(-2, -1)
        padded_sequences = sequences.to_tensor(default_value=0, shape=[None, max_length])
        return padded_sequences
    
    encoder_input_data = format_sequences(fr_tokenizer, train_fr, max_length)
    decoder_input_data = format_sequences(fr_tokenizer, ["[START]" + sentence for sentence in train_fr], max_length)
    decoder_target_data = format_sequences(en_tokenizer, [sentence +"[END]" for sentence in train_en], max_length)
    vdecoder_input_data = format_sequences(fr_tokenizer, ["[START]" + sentence for sentence in vfrench_sentences_decoded], max_length)
    vencoder_input_data = format_sequences(fr_tokenizer, vfrench_sentences_decoded, max_length)
    vdecoder_target_data = format_sequences(en_tokenizer, [sentence +"[END]" for sentence in venglish_sentences_decoded], max_length)
    from tensorflow.keras.utils import  plot_model
    from tensorflow.keras.callbacks import ModelCheckpoint, EarlyStopping

    batch_size = 64
    epochs = 33

    model.fit([encoder_input_data, decoder_input_data], decoder_target_data,
            batch_size=batch_size,
            epochs=epochs,
            validation_data=([vencoder_input_data, vdecoder_input_data],vdecoder_target_data))
    print("le modele a été fait ")
    model.save_weights('CACATOR.h5')   
    vol.commit()
    print("le modele a été fait ")
    
    model_store_path = "/vol/models"
    save(model_store_path, model)
    return encoder_input_data,decoder_input_data,decoder_input_data,vdecoder_input_data,vencoder_input_data,decoder_input_data




@app.local_entrypoint()

def main():
    print(download.remote())