Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 25 additions & 26 deletions docs/source/en/guides/manage-spaces.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ rendered properly in your Markdown viewer.

In this guide, we will see how to manage your Space runtime
([secrets](https://huggingface.co/docs/hub/spaces-overview#managing-secrets),
[hardware](https://huggingface.co/docs/hub/spaces-gpus), and [storage](https://huggingface.co/docs/hub/spaces-storage#persistent-storage)) using `huggingface_hub`.
[hardware](https://huggingface.co/docs/hub/spaces-gpus), and volumes) using `huggingface_hub`.

## A simple example: configure secrets and hardware.

Expand Down Expand Up @@ -132,7 +132,6 @@ Upgraded hardware will be automatically assigned to your Space once it's built.
... repo_type="space",
... space_sdk="gradio"
... space_hardware="cpu-upgrade",
... space_storage="small",
... space_sleep_time="7200", # 2 hours in secs
... )
```
Expand All @@ -141,7 +140,6 @@ Upgraded hardware will be automatically assigned to your Space once it's built.
... from_id=repo_id,
... repo_type="space",
... space_hardware="cpu-upgrade",
... space_storage="small",
... space_sleep_time="7200", # 2 hours in secs
... )
```
Expand Down Expand Up @@ -201,41 +199,42 @@ Upgraded hardware will be automatically assigned to your Space once it's built.
... )
```

**6. Add persistent storage to your Space**
**6. Mount volumes in your Space**

You can choose the storage tier of your choice to access disk space that persists across restarts of your Space. This means you can read and write from disk like you would with a traditional hard drive. See [docs](https://huggingface.co/docs/hub/spaces-storage#persistent-storage) for more details.
You can mount Hub resources (models, datasets, or storage buckets) as volumes in your Space's container. This gives your Space direct filesystem access to these resources without having to download them in your code.

```py
>>> from huggingface_hub import SpaceStorage
>>> api.request_space_storage(repo_id=repo_id, storage=SpaceStorage.LARGE)
>>> from huggingface_hub import Volume
>>> api.set_space_volumes(
... repo_id=repo_id,
... volumes=[
... Volume(type="model", source="username/my-model", mount_path="/models", read_only=True),
... Volume(type="dataset", source="username/my-dataset", mount_path="/data", read_only=True),
... Volume(type="bucket", source="username/my-bucket", mount_path="/output"),
... ],
... )
```

You can also delete your storage, losing all the data permanently.
You can check which volumes are currently mounted via the Space runtime:

```py
>>> api.delete_space_storage(repo_id=repo_id)
>>> runtime = api.get_space_runtime(repo_id=repo_id)
>>> runtime.volumes
[Volume(type='model', source='username/my-model', mount_path='/models', read_only=True), ...]
```

Note: You cannot decrease the storage tier of your space once it's been granted. To do so,
you must delete the storage first then request the new desired tier.

**Bonus: request storage when creating or duplicating the Space!**
To remove all volumes from your Space:

```py
>>> api.create_repo(
... repo_id=repo_id,
... repo_type="space",
... space_sdk="gradio"
... space_storage="large",
... )
```
```py
>>> api.duplicate_repo(
... from_id=repo_id,
... repo_type="space",
... space_storage="large",
... )
>>> api.delete_space_volumes(repo_id=repo_id)
```

> [!NOTE]
> Models, datasets, and Spaces are always mounted as read-only. Only storage buckets support read-write mounts.

> [!WARNING]
> Setting volumes replaces any previously mounted volumes. To add a volume to an existing list, first read the current volumes from the runtime and include them in the new list.

## More advanced: temporarily upgrade your Space !

Spaces allow for a lot of different use cases. Sometimes, you might want
Expand Down
4 changes: 2 additions & 2 deletions docs/source/en/package_reference/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -2928,7 +2928,7 @@ $ hf repos create [OPTIONS] REPO_ID
* `--exist-ok / --no-exist-ok`: Do not raise an error if repo already exists. [default: no-exist-ok]
* `--resource-group-id TEXT`: Resource group in which to create the repo. Resource groups is only available for Enterprise Hub organizations.
* `--flavor [cpu-basic|cpu-upgrade|cpu-performance|cpu-xl|sprx8|zero-a10g|t4-small|t4-medium|l4x1|l4x4|l40sx1|l40sx4|l40sx8|a10g-small|a10g-large|a10g-largex2|a10g-largex4|a100-large|a100x4|a100x8|h200|h200x2|h200x4|h200x8|inf2x6]`: Space hardware flavor (e.g. 'cpu-basic', 't4-medium', 'l4x4'). Only for Spaces.
* `--storage [small|medium|large]`: Space persistent storage tier ('small', 'medium', or 'large'). Only for Spaces.
* `--storage [small|medium|large]`: (Deprecated, use volumes instead) Space persistent storage tier ('small', 'medium', or 'large'). Only for Spaces.
* `--sleep-time INTEGER`: Seconds of inactivity before the Space is put to sleep. Use -1 to disable. Only for Spaces.
* `-s, --secrets TEXT`: Set secret environment variables. E.g. --secrets SECRET=value or `--secrets HF_TOKEN` to pass your Hugging Face token.
* `--secrets-file TEXT`: Read in a file of secret environment variables.
Expand Down Expand Up @@ -3034,7 +3034,7 @@ $ hf repos duplicate [OPTIONS] FROM_ID [TO_ID]
* `--token TEXT`: A User Access Token generated from https://huggingface.co/settings/tokens.
* `--exist-ok / --no-exist-ok`: Do not raise an error if repo already exists. [default: no-exist-ok]
* `--flavor [cpu-basic|cpu-upgrade|cpu-performance|cpu-xl|sprx8|zero-a10g|t4-small|t4-medium|l4x1|l4x4|l40sx1|l40sx4|l40sx8|a10g-small|a10g-large|a10g-largex2|a10g-largex4|a100-large|a100x4|a100x8|h200|h200x2|h200x4|h200x8|inf2x6]`: Space hardware flavor (e.g. 'cpu-basic', 't4-medium', 'l4x4'). Only for Spaces.
* `--storage [small|medium|large]`: Space persistent storage tier ('small', 'medium', or 'large'). Only for Spaces.
* `--storage [small|medium|large]`: (Deprecated, use volumes instead) Space persistent storage tier ('small', 'medium', or 'large'). Only for Spaces.
* `--sleep-time INTEGER`: Seconds of inactivity before the Space is put to sleep. Use -1 to disable. Only for Spaces.
* `-s, --secrets TEXT`: Set secret environment variables. E.g. --secrets SECRET=value or `--secrets HF_TOKEN` to pass your Hugging Face token.
* `--secrets-file TEXT`: Read in a file of secret environment variables.
Expand Down
10 changes: 8 additions & 2 deletions src/huggingface_hub/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,6 @@
"JobOwner",
"JobStage",
"JobStatus",
"Volume",
],
"_login": [
"auth_list",
Expand All @@ -109,6 +108,7 @@
"SpaceStage",
"SpaceStorage",
"SpaceVariable",
"Volume",
],
"_tensorboard_logger": [
"HFSummaryWriter",
Expand Down Expand Up @@ -229,6 +229,7 @@
"delete_space_secret",
"delete_space_storage",
"delete_space_variable",
"delete_space_volumes",
"delete_tag",
"delete_webhook",
"disable_space_dev_mode",
Expand Down Expand Up @@ -319,6 +320,7 @@
"run_uv_job",
"scale_to_zero_inference_endpoint",
"set_space_sleep_time",
"set_space_volumes",
"space_info",
"super_squash_history",
"suspend_scheduled_job",
Expand Down Expand Up @@ -927,6 +929,7 @@
"delete_space_secret",
"delete_space_storage",
"delete_space_variable",
"delete_space_volumes",
"delete_tag",
"delete_webhook",
"disable_space_dev_mode",
Expand Down Expand Up @@ -1053,6 +1056,7 @@
"set_async_client_factory",
"set_client_factory",
"set_space_sleep_time",
"set_space_volumes",
"snapshot_download",
"space_info",
"split_state_dict_into_shards_factory",
Expand Down Expand Up @@ -1207,7 +1211,6 @@ def __dir__():
JobOwner, # noqa: F401
JobStage, # noqa: F401
JobStatus, # noqa: F401
Volume, # noqa: F401
)
from ._login import (
auth_list, # noqa: F401
Expand All @@ -1231,6 +1234,7 @@ def __dir__():
SpaceStage, # noqa: F401
SpaceStorage, # noqa: F401
SpaceVariable, # noqa: F401
Volume, # noqa: F401
)
from ._tensorboard_logger import HFSummaryWriter # noqa: F401
from ._webhooks_payload import (
Expand Down Expand Up @@ -1349,6 +1353,7 @@ def __dir__():
delete_space_secret, # noqa: F401
delete_space_storage, # noqa: F401
delete_space_variable, # noqa: F401
delete_space_volumes, # noqa: F401
delete_tag, # noqa: F401
delete_webhook, # noqa: F401
disable_space_dev_mode, # noqa: F401
Expand Down Expand Up @@ -1439,6 +1444,7 @@ def __dir__():
run_uv_job, # noqa: F401
scale_to_zero_inference_endpoint, # noqa: F401
set_space_sleep_time, # noqa: F401
set_space_volumes, # noqa: F401
space_info, # noqa: F401
super_squash_history, # noqa: F401
suspend_scheduled_job, # noqa: F401
Expand Down
54 changes: 3 additions & 51 deletions src/huggingface_hub/_jobs_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,51 +14,13 @@
from dataclasses import dataclass
from datetime import datetime
from enum import Enum
from typing import Any, Literal
from typing import Any

from huggingface_hub import constants
from huggingface_hub._space_api import SpaceHardware
from huggingface_hub._space_api import SpaceHardware, Volume
from huggingface_hub.utils._datetime import parse_datetime


@dataclass
class Volume:
"""
Describes a volume to mount in a Job container.

Args:
type (`str`):
Type of volume: `"bucket"`, `"model"`, `"dataset"`, or `"space"`.
source (`str`):
Source identifier, e.g. `"username/my-bucket"` or `"username/my-model"`.
mount_path (`str`):
Mount path inside the container, e.g. `"/data"`. Must start with `/`.
revision (`str` or `None`):
Git revision (only for repos, defaults to `"main"`).
read_only (`bool` or `None`):
Read-only mount. Forced `True` for repos, defaults to `False` for buckets.
path (`str` or `None`):
Subfolder prefix inside the bucket/repo to mount, e.g. `"path/to/dir"`.
"""

type: Literal["bucket", "model", "dataset", "space"]
source: str
mount_path: str
revision: str | None = None
read_only: bool | None = None
path: str | None = None

def __init__(self, **kwargs) -> None:
self.type = kwargs.get("type", "model")
self.source = kwargs["source"]
mount_path = kwargs.get("mountPath")
self.mount_path = mount_path if mount_path is not None else kwargs["mount_path"]
self.revision = kwargs.get("revision")
read_only = kwargs.get("readOnly")
self.read_only = read_only if read_only is not None else kwargs.get("read_only")
self.path = kwargs.get("path")


class JobStage(str, Enum):
"""
Enumeration of possible stage of a Job on the Hub.
Expand Down Expand Up @@ -432,17 +394,7 @@ def _create_job_spec(
job_spec["labels"] = labels
# volumes are optional
if volumes:
job_spec["volumes"] = [
{
"type": vol.type,
"source": vol.source,
"mountPath": vol.mount_path,
**({"revision": vol.revision} if vol.revision is not None else {}),
**({"readOnly": vol.read_only} if vol.read_only is not None else {}),
**({"path": vol.path} if vol.path is not None else {}),
}
for vol in volumes
]
job_spec["volumes"] = [vol.to_dict() for vol in volumes]
# input is either from docker hub or from HF spaces
for prefix in (
"https://huggingface.co/spaces/",
Expand Down
59 changes: 59 additions & 0 deletions src/huggingface_hub/_space_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,59 @@ class SpaceStorage(str, Enum):
LARGE = "large"


@dataclass
class Volume:
"""
Describes a volume to mount in a Space or Job container.

Args:
type (`str`):
Type of volume: `"bucket"`, `"model"`, `"dataset"`, or `"space"`.
source (`str`):
Source identifier, e.g. `"username/my-bucket"` or `"username/my-model"`.
mount_path (`str`):
Mount path inside the container, e.g. `"/data"`. Must start with `/`.
revision (`str` or `None`):
Git revision (only for repos, defaults to `"main"`).
read_only (`bool` or `None`):
Read-only mount. Forced `True` for repos, defaults to `False` for buckets.
path (`str` or `None`):
Subfolder prefix inside the bucket/repo to mount, e.g. `"path/to/dir"`.
"""

type: Literal["bucket", "model", "dataset", "space"]
source: str
mount_path: str
revision: str | None = None
read_only: bool | None = None
path: str | None = None

def __init__(self, **kwargs) -> None:
self.type = kwargs.get("type", "model")
self.source = kwargs["source"]
mount_path = kwargs.get("mountPath")
self.mount_path = mount_path if mount_path is not None else kwargs["mount_path"]
self.revision = kwargs.get("revision")
read_only = kwargs.get("readOnly")
self.read_only = read_only if read_only is not None else kwargs.get("read_only")
self.path = kwargs.get("path")

def to_dict(self) -> dict:
"""Serialize to the JSON payload expected by the Hub API."""
data: dict = {
"type": self.type,
"source": self.source,
"mountPath": self.mount_path,
}
if self.revision is not None:
data["revision"] = self.revision
if self.read_only is not None:
data["readOnly"] = self.read_only
if self.path is not None:
data["path"] = self.path
return data


@dataclass
class SpaceHotReloading:
status: Literal["created", "canceled"]
Expand Down Expand Up @@ -140,6 +193,9 @@ class SpaceRuntime:
Number of seconds the Space will be kept alive after the last request. By default (if value is `None`), the
Space will never go to sleep if it's running on an upgraded hardware, while it will go to sleep after 48
hours on a free 'cpu-basic' hardware. For more details, see https://huggingface.co/docs/hub/spaces-gpus#sleep-time.
volumes (`list[Volume]` or `None`):
List of volumes mounted in the Space. Each volume is a [`Volume`] object describing its type, source,
mount path, and optional settings. `None` if no volumes are attached.
raw (`dict`):
Raw response from the server. Contains more information about the Space
runtime like number of replicas, number of cpu, memory size,...
Expand All @@ -151,6 +207,7 @@ class SpaceRuntime:
sleep_time: int | None
storage: SpaceStorage | None
hot_reloading: SpaceHotReloading | None
volumes: list[Volume] | None
raw: dict

def __init__(self, data: dict) -> None:
Expand All @@ -160,6 +217,8 @@ def __init__(self, data: dict) -> None:
self.sleep_time = data.get("gcTimeout")
self.storage = data.get("storage")
self.hot_reloading = SpaceHotReloading(raw_hr) if (raw_hr := data.get("hotReloading")) is not None else None
raw_volumes = data.get("volumes")
self.volumes = [Volume(**v) for v in raw_volumes] if raw_volumes is not None else None
self.raw = data


Expand Down
3 changes: 1 addition & 2 deletions src/huggingface_hub/cli/jobs.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,7 @@

import typer

from huggingface_hub import SpaceHardware, constants
from huggingface_hub._jobs_api import Volume
from huggingface_hub import SpaceHardware, Volume, constants
from huggingface_hub.errors import CLIError, HfHubHTTPError
from huggingface_hub.utils import logging
from huggingface_hub.utils._cache_manager import _format_size
Expand Down
2 changes: 1 addition & 1 deletion src/huggingface_hub/cli/repos.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ class GatedChoices(str, enum.Enum):
SpaceStorage | None,
typer.Option(
"--storage",
help="Space persistent storage tier ('small', 'medium', or 'large'). Only for Spaces.",
help="(Deprecated, use volumes instead) Space persistent storage tier ('small', 'medium', or 'large'). Only for Spaces.",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no CLI replacement yet right?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no not yet (see https://github.com/huggingface-internal/moon-landing/pull/17543). I'll handle it in a separate PR once it's released server-side

),
]

Expand Down
Loading
Loading