Skip to content
  • AIP1 Isambard-AI Phase 1 supported
  • AIP2 Isambard-AI Phase 2 supported
  • I3 Isambard 3 supported
  • BC5 BlueCrystal 5 unsupported

Packaging Datasets with SquashFS

Abstract

This tutorial introduces SquashFS as a method for packaging large datasets on Isambard supercomputers. SquashFS is a compressed, read-only filesystem that is particularly well-suited to storing datasets composed of many small files, which can otherwise cause performance problems. By the end of the tutorial, users will know how to create, inspect, and use SquashFS images in batch jobs and containers.

Prerequisites

Users will need to have accepted an invitation to a project on a BriCS service, and completed the setup tutorial. Familiarity with the Linux command line, conda and basic Slurm job submission (covered in the Installing and Running Software tutorial) is assumed.

Learning Objectives

By the end of this tutorial, users will have an understanding of how to:

  • How using SquashFS can make their work run faster
  • How to create a SquashFS image
  • How to mount and use a SquashFS image both with and without a container
  • Why using SquashFS is beneficial for working with datasets containing many files

Table of Contents

Logging in via SSH

Use the Login Guide if needed to log in. The general format for logging in is:

ssh [PROJECT].[FACILITY].isambard

Why SquashFS?

Parallel filesystems such as those used on BriCS platforms are optimised for large sequential reads and writes. They can perform relatively poorly when working with very large numbers of small files, because each operation generates a separate metadata request to the filesystem servers. This can result in a loss of overall application performance.

This issue is especially relevant to machine learning datasets, which can consist of millions of individual image or text files. Moreover, storage quotas place a limit on the number of files as well as total disk space.

SquashFS addresses these issues by packing an entire directory tree into a single compressed archive file. The dataset is then accessed by mounting this single file, dramatically reducing the metadata load on the filesystem and potentially increasing overall application performance.

SquashFS is also read-only, which suits datasets that are written once and read many times and reduces the risks of accidental editing of the data.

Use the Storage spaces information page to review the storage locations available to you and their intended use before starting the exercises.

Defining a simple example

For this tutorial, we will use the Street View House Numbers (SVHN) dataset as a small but illustrative example of a many-file dataset. It consists of around 33,000 .png files each a few kilobytes in size, with the overall dataset using 587 MB.

We will perform a simple resizing action on each file to create a toy workload that tries to rapidly read through the dataset. We will use the Pillow Python module installed via a conda environment, and compare performance with and without using SquashFS.

Setting up the conda environment

Follow the early steps of the Python guide to install Miniforge.

Now we will activate conda. When conda is activated, it starts in the base environment by default.

user.project@login44:~> source ~/miniforge3/bin/activate
(base) user.project@login44:~>

Now we will create a new environment for this tutorial and install Pillow:

(base) user.project@login44:~> conda create -n squashfs_tut python=3.14.0
(base) user.project@login44:~> conda activate squashfs_tut
(squashfs_tut) user.project@login44:~> conda install Pillow

These steps will take a few moments while the packages are downloaded and installed.

You can verify the environment is set up correctly by running this command that returns no output if successful:

(squashfs_tut) user.project@login44:~> python -c "import PIL"

Testing on a single file

The uncompressed dataset has been downloaded and uncompressed as /projects/brics/public/data/svhn_dataset/train.

Warning

Please do not create your own copies of the dataset.

We can briefly demonstrate performing the resize on a single file interactively in Python. It runs effectively instantly.

(squashfs_tut) user.project@login44:~> python
Python 3.14.0 | packaged by conda-forge | (main, Dec  2 2025, 19:50:15) [GCC 14.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from PIL import Image
>>> with Image.open("/projects/brics/public/svhn_dataset/train/20011.png") as img:
...     print(img.size)
...     resized = img.resize((800, 600))
...     print(resized.size)
...
(74, 32)
(800, 600)
>>> exit()
(squashfs_tut) user.project@login44:~>

Creating a SquashFS file on Isambard

Tip

If you are uploading a dataset to Isambard for the first time you can create the SquashFS image on your local machine before transferring it. See Preparing a SquashFS image locally and upload to Isambard to skip ahead.

Now we will create a SquashFS image from these data. There are many different options available and the example provided here has been chosen to be simple and minimise CPU overhead due to decompression. We will create it on the same type of storage as the original data to allow a fair comparison.

There are two key commands when creating and using SquashFS images:

  • mksquashfs.static to create the image
  • squashfuse to mount the image in your user space so it can be read like a normal directory

For large datasets, the one-off creation of the image can be resource intensive, so we will use a compute node.

Note

For Isambard 3, users should swap --gpus=1 for --nodes=1 due to the differing hardware on each platform.

user.project@login40:~> srun --gpus=1 --time=00:15:00 --pty /bin/bash --login
srun: job 1566942 queued and waiting for resources
srun: job 1566942 has been allocated resources

user.project@login40:~> SOURCE_DIR=/projects/brics/public/svhn_dataset/train
user.project@login40:~> SQUASHFS=$PROJECTDIR/svhn_tutorial.squashfs
user.project@login40:~> mksquashfs.static $SOURCE_DIR $SQUASHFS -no-compression -no-xattrs -processors 72

To exit the interactive session on the compute node, use Ctrl+D or type logout once. Repeating will log you out of the login node.

Optional: Preparing a SquashFS image locally and upload to Isambard

If you are uploading a dataset for the first time you can create the SquashFS image on your local machine before transferring it. This avoids writing individual files to the parallel filesystem at all and is the preferred approach for new data.

If you'd like to try that with this tutorial download the dataset linked to above on to your computer and then extract it. You can then create the SquashFS image on your computer using these instructions and uploading the correctly named image to Isambard.

macOS

Install squashfs-tools using either Homebrew or MacPorts:

brew install squashfs
sudo port install squashfs-tools

Then create the image:

mksquashfs /path/to/your/dataset mydata.squashfs -no-compression -no-xattrs

Windows (WSL)

The recommended approach on Windows is to use the Windows Subsystem for Linux (WSL). With an Ubuntu WSL environment, install squashfs-tools:

sudo apt install squashfs-tools

Your Windows drives are accessible under /mnt/ in WSL, so a folder at C:\Users\username\dataset becomes /mnt/c/Users/username/dataset:

mksquashfs /mnt/c/Users/you/dataset mydata.squashfs -no-compression -no-xattrs

See the file transfer guide for instructions on how to transfer the resulting image to Isambard.

Mounting SquashFS and Testing on a single SquashFS file

Now we can adapt the above example to use this image. There are 3 simple changes:

  • Mounting the SquashFS image
  • Changing the path used in the Python
  • Unmounting the SquashFS image at the end
(squashfs_tut) user.project@login44:~> mkdir -p $SCRATCH/squashfs_tut
(squashfs_tut) user.project@login44:~> squashfuse $PROJECTDIR/svhn_tutorial.squashfs $SCRATCH/squashfs_tut

(squashfs_tut) user.project@login44:~> python
Python 3.14.0 | packaged by conda-forge | (main, Dec  2 2025, 19:50:15) [GCC 14.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import os
>>> from PIL import Image
>>> with Image.open(f"{os.environ['SCRATCH']}/squashfs_tut/20011.png") as img:
...     print(img.size)
...     resized = img.resize((800, 600))
...     print(resized.size)
...
(74, 32)
(800, 600)
>>> exit()
(squashfs_tut) user.project@login44:~> fusermount -u $SCRATCH/squashfs_tut

Running on a compute node with and without SquashFS

Now we will run the resize on a large number of files on a compute node using both the original dataset and the SquashFS image we have created.

We will adapt the interactive python example to create a script that loops through the entire directory using an environment variable to easily swap between the two locations. Create resize_files.py in a convenient location:

import os
import pathlib
from PIL import Image

# Get the path to the .png files from an environment variable
pngdir = pathlib.Path(os.environ.get("PNG_FILE_DIR"))

# Loop through every .png file in the directory
for file in pngdir.glob("*.png"):
    with Image.open(file) as img:
        resized = img.resize((800, 600))

Now we can start an interactive session on a compute node and activate the conda environment:

user.project@login44:~> srun --gpus=1 --time=00:15:00 --pty /bin/bash --login

user.project@login44:~> source ~/miniforge3/bin/activate
(base) user.project@nid010581:~> conda activate squashfs_tut

We run this script on the normal files using perf stat to provide a basic measure of the runtime.

(squashfs_tut) user.project@nid010581:~> export PNG_FILE_DIR=/projects/brics/public/svhn_dataset/train
(squashfs_tut) user.project@nid010581:~> perf stat python resize_files.py

 Performance counter stats for 'python resize_files.py':

[snip]

     143.193323967 seconds time elapsed

      88.432107000 seconds user
       6.722603000 seconds sys

Then when mounting and using the SquashFS image:

(squashfs_tut) user.project@nid010581:~> mkdir -p $SCRATCH/squashfs_tut
(squashfs_tut) user.project@nid010581:~> squashfuse $PROJECTDIR/svhn_tutorial.squashfs $SCRATCH/squashfs_tut
(squashfs_tut) user.project@nid010581:~> export PNG_FILE_DIR=$SCRATCH/squashfs_tut
(squashfs_tut) user.project@nid010581:~> perf stat python resize_files.py

 Performance counter stats for 'python resize_files.py':

[snip]

      91.470816951 seconds time elapsed

      87.091545000 seconds user
       2.109857000 seconds sys

The job completes significantly faster when using SquashFS.

To exit the interactive session on the compute node, use Ctrl+D or type logout once. Repeating will log you out of the login node.

Mounting SquashFS in containers

It is also possible to mount SquashFS images inside a container just like a normal directory. To demonstrate this we use the lolcow container, which is covered in more detail in the Installing and Running Software tutorial.

Tip

Keeping your datasets separate from your containers is good practice.

If you don't already have a copy create a fresh directory, and then pull and build the container:

user.project@login41:~> mkdir sif-images
user.project@login41:~> cd sif-images/
user.project@login41:~/sif-images> ls
user.project@login41:~/sif-images> singularity build lolcow.sif docker://sylabsio/lolcow
INFO:    Starting build...
INFO:    Fetching OCI image...
25.9MiB / 25.9MiB [===============================================================================================================] 100 % 16.5 MiB/s 0s
43.2MiB / 43.2MiB [===============================================================================================================] 100 % 16.5 MiB/s 0s
INFO:    Extracting OCI image...
INFO:    Inserting Apptainer configuration...
INFO:    Creating SIF file...
[============================================================================================================================================] 100 % 0s
INFO:    Build complete: lolcow.sif
user.project@login40:~/sif-images> ls
lolcow.sif

Now we can start an interactive session and use --bind flag to directly mount the image into /tmp. We can demonstrate this has worked by printing the number of files in the directory by piping the outout of the directory listing to word count. As mentioned at the start of the tutorial, there are around 33,000 files:

user.project@login40:~/sif-images> singularity shell --bind $PROJECTDIR/svhn_tutorial.squashfs:/tmp:image-src=/ lolcow.sif
Apptainer> cowsay $(ls /tmp/ | wc -w)
 _______
< 33404 >
 -------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||
Apptainer>

For further information on using containers, please see the containers guide.