Skip to content

Commit cab4152

Browse files
omarespejelOmar Espejelosanseviero
authoredApr 26, 2022
Add fastai upstream and downstream capacities for fastai>=2.4 and fastcore>=1.3.27 versions (#678)
* added a fastai mixin for upstream and downstream tasks with fastai library * Added check for right fastai version * Added aditional documentation * Add support for fastai>=2.4 versions and fastcore>=1.3.27 * Update docstrings, save_fastai_learner, push_to_hub_fastai, and change name from fastai_mixin * trim triling whitespaces * Import fastai_utils.py in huggingface_hub/__init__py * Add check_fastai_fastcore_versions function in fastai_utils.py * Eliminate imports of libraries not used in fastai_utils.py * Add isort and black format to fastai_utils.py * Change pickle_protocol argument from kwargs to explicit * Change kwargs arguments in the function from_pretrained_fastai to explicit * Simplify push_to_hub_fastai function, particularly the repo_id argument * Eliminate search for a pickle document in from_pretrained_fastai function * Simplify push_to_hub_fastai and correct bug in from_pretrained_fastai * Add pickle.DEFAULT_PROTOCOL for get adequate protocol when exporting the Learners * Allow to load only models in the Hub when using from_pretrained_fastai * Eliminate cache_dir from from_pretrained_fastai for simplification * Correct nit picks in push_to_hub_fastai * Update with nits * Apply isort * Replace config.json for pyproject.toml to check for fastai and fastcore versions * Isort imports * Make pyproject.toml automatically filled with fastai, fastcore, and python versions used. * add check_fastai_fastcore_pyproject_versions function to know the fastai and fastcore versions of the pretrained model to load from the Hub * Change library tomlkit for toml * Add extras[fastai] with the toml library * Change the way the token is asked in def push_to_hub_fastai( * Eliminate logger from imports * Fix nits * Remove typing.Union from imports * add fastai integration tests * Import fastai in setup.py * Import toml inside check_fastai_fastcore_pyproject_versions function * Nits in fastai_utils.py * Add build_fastai to python-tests.yml * Add fastcore import to setup.py * Add require_fastai_fastcore() to skip tests * Nits and documentation of raised errors improved * add strategy for fastai in python-tests.yml * Eliminate organization from push_to_hub_fastai * Add python 3.7-3.10 to python-tests.yml * Change python version in tests to 3.9 * Fix tests * Fix conflict in config.py due to order of tf packages * Fix delete_repo function in test_fastai_integration * Isort test_fastai_integration * Replace the repo_id name for model_id * Keep the names consistent * Make fastai and fastcore versions flexible in setup.py * Would be "fastai>=2.4" and "fastcore>=1.3.27" * Confirm fastai supports python 3.10 in python-tests-yml * Version 2.5.6 of fastai supports python 3.10 * Fix docs in fastai_utils.py * Fix functions' arguments documentation for a proper rendering * Change the name of DummyModel for dummy_model * Handle pickling errors when exporting a fastai.Learner * Guide user in how to deal with a PicklingError * Change name of internal functions in fastai_utils.py * Start functions that are not push_to_hub_fastai and from_pretrained_fastai with a "_" to indicate that they are internal functions. They would not be necessary for the user. * black style to fastai_utils.py * Eliminate unnecessary comments from fastai_utils.py * Add capacity to load a local fastai.Learner to from_pretrained_keras * In fastai_utils.py. * This would be in addition to being able to load a pretrained model from the Hub * black format fastai_utils.py * Come back to Python 3.9 instead of 3.10 * fastai was made compatible with Python 3.10 just in the most recent version 3.5.6 released on April 1st. I would prefer to wait for the next release of fastai. * Change name name of save_fastai_learner to _save_pretrained_fastai in test_fastai_integration.py * Change the name to _save_pretrained_fastai in __init__.py * Additionally, isort test_fastai_integration.py * Change the name from save_fastai_learner to _save_pretrained_fastai * isort __init__.py * Fix nits in test_fastai_integration.py * Add fastai integration to docs * functions from_pretrained_fastai and push_to_hub_fastai were added to mixins.mdx * Fix nits * Fix wording * Allow _save_pretrained_fastai to directly export the model in save_directory * By default learner.export saves learner to learner.path * Before we where saving the model in learner.path and them moving it to save_directory * Fix by changing learner.path to being equal to save_directory * black fastai_utils.py * Make the requirement of having a pyproject.toml optional * Additionally, makes optional that the pyproject.toml contains the fastai and fastcore versions * Will continue to throw an error if the toml library is not available * Will continue to thrown an error if the pyproject.toml specifies fastai or fastcore versions that are not supported by from_pretrained_fastai * This will allow to load fastai models from the Hub that were not necessarily uploaded using push_to_hub_fastai. * Add warnings if the pyproject.toml does not contain a "build-system" and "requires" section * Change try-excepts for ifs in the warnings checking the fastai and fastcore versions * Fix nits in documentation * Move errors in _check_fastai_fastcore_pyproject_versions to conditions after warnings * Misc improvements * Move the versions checks for fastai and fastcore to "else"'s * This allows to eliminate the returns when checking for the versions of fastai and fastcore in the pyproject.toml * Move the versions checks for fastai and fastcore to "else"'s * This allows to eliminate the returns when checking for the versions of fastai and fastcore in the pyproject.toml * Black reformat Co-authored-by: Omar Espejel <[email protected]> Co-authored-by: osanseviero <[email protected]>
1 parent 04c38fc commit cab4152

File tree

7 files changed

+639
-1
lines changed

7 files changed

+639
-1
lines changed
 

‎.github/workflows/python-tests.yml

+21
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,27 @@ jobs:
9090
pip install .[testing,tensorflow]
9191
9292
- run: pytest -Werror::FutureWarning -sv ./tests/test_keras*
93+
94+
build_fastai:
95+
runs-on: ubuntu-latest
96+
strategy:
97+
matrix:
98+
python-version: ["3.7", "3.9"]
99+
100+
steps:
101+
- uses: actions/checkout@v2
102+
103+
- name: Set up Python ${{ matrix.python-version }}
104+
uses: actions/setup-python@v2
105+
with:
106+
python-version: ${{ matrix.python-version }}
107+
108+
- name: Install dependencies
109+
run: |
110+
pip install --upgrade pip
111+
pip install .[testing,fastai]
112+
113+
- run: pytest -Werror::FutureWarning -sv ./tests/test_fastai*
93114

94115
tests_lfs:
95116
runs-on: ubuntu-latest

‎docs/source/package_reference/mixins.mdx

+10-1
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,13 @@ objects, in order to provide simple uploading and downloading functions.
1717

1818
[[autodoc]] save_pretrained_keras
1919

20-
[[autodoc]] KerasModelHubMixin
20+
[[autodoc]] KerasModelHubMixin
21+
22+
### Fastai
23+
24+
[[autodoc]] from_pretrained_fastai
25+
26+
[[autodoc]] push_to_hub_fastai
27+
28+
29+

‎setup.py

+6
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ def get_version() -> str:
2727
"torch",
2828
]
2929

30+
extras["fastai"] = [
31+
"toml",
32+
"fastai>=2.4",
33+
"fastcore>=1.3.27",
34+
]
35+
3036
extras["tensorflow"] = ["tensorflow", "pydot", "graphviz"]
3137

3238
extras["testing"] = [

‎src/huggingface_hub/__init__.py

+5
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@
3131
TF2_WEIGHTS_NAME,
3232
TF_WEIGHTS_NAME,
3333
)
34+
from .fastai_utils import (
35+
_save_pretrained_fastai,
36+
from_pretrained_fastai,
37+
push_to_hub_fastai,
38+
)
3439
from .file_download import cached_download, hf_hub_download, hf_hub_url
3540
from .hf_api import (
3641
DatasetSearchArguments,

‎src/huggingface_hub/fastai_utils.py

+438
Large diffs are not rendered by default.

‎src/huggingface_hub/file_download.py

+36
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,22 @@ def is_graphviz_available():
9797
except importlib_metadata.PackageNotFoundError:
9898
pass
9999

100+
_fastai_version = "N/A"
101+
_fastai_available = False
102+
try:
103+
_fastai_version: str = importlib_metadata.version("fastai")
104+
_fastai_available = True
105+
except importlib_metadata.PackageNotFoundError:
106+
pass
107+
108+
_fastcore_version = "N/A"
109+
_fastcore_available = False
110+
try:
111+
_fastcore_version: str = importlib_metadata.version("fastcore")
112+
_fastcore_available = True
113+
except importlib_metadata.PackageNotFoundError:
114+
pass
115+
100116

101117
def is_torch_available():
102118
return _torch_available
@@ -106,6 +122,22 @@ def is_tf_available():
106122
return _tf_available
107123

108124

125+
def is_fastai_available():
126+
return _fastai_available
127+
128+
129+
def get_fastai_version():
130+
return _fastai_version
131+
132+
133+
def is_fastcore_available():
134+
return _fastcore_available
135+
136+
137+
def get_fastcore_version():
138+
return _fastcore_version
139+
140+
109141
@_deprecate_positional_args
110142
def hf_hub_url(
111143
repo_id: str,
@@ -275,6 +307,10 @@ def http_user_agent(
275307
ua += f"; torch/{_torch_version}"
276308
if is_tf_available():
277309
ua += f"; tensorflow/{_tf_version}"
310+
if is_fastai_available():
311+
ua += f"; fastai/{_fastai_version}"
312+
if is_fastcore_available():
313+
ua += f"; fastcore/{_fastcore_version}"
278314
if isinstance(user_agent, dict):
279315
ua += "; " + "; ".join(f"{k}/{v}" for k, v in user_agent.items())
280316
elif isinstance(user_agent, str):

‎tests/test_fastai_integration.py

+123
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import os
2+
import shutil
3+
import time
4+
import uuid
5+
from unittest import TestCase, skip
6+
7+
from huggingface_hub import HfApi
8+
from huggingface_hub.fastai_utils import (
9+
_save_pretrained_fastai,
10+
from_pretrained_fastai,
11+
push_to_hub_fastai,
12+
)
13+
from huggingface_hub.file_download import (
14+
is_fastai_available,
15+
is_fastcore_available,
16+
is_torch_available,
17+
)
18+
19+
from .testing_constants import ENDPOINT_STAGING, TOKEN, USER
20+
from .testing_utils import set_write_permission_and_retry
21+
22+
23+
def repo_name(id=uuid.uuid4().hex[:6]):
24+
return "fastai-repo-{0}-{1}".format(id, int(time.time() * 10e3))
25+
26+
27+
WORKING_REPO_SUBDIR = f"fixtures/working_repo_{__name__.split('.')[-1]}"
28+
WORKING_REPO_DIR = os.path.join(
29+
os.path.dirname(os.path.abspath(__file__)), WORKING_REPO_SUBDIR
30+
)
31+
32+
if is_fastai_available():
33+
from fastai.data.block import DataBlock
34+
from fastai.test_utils import synth_learner
35+
36+
if is_torch_available():
37+
import torch
38+
39+
40+
def require_fastai_fastcore(test_case):
41+
"""
42+
Decorator marking a test that requires fastai and fastcore.
43+
These tests are skipped when fastai and fastcore are not installed.
44+
"""
45+
if not is_fastai_available():
46+
return skip("Test requires fastai")(test_case)
47+
elif not is_fastcore_available():
48+
return skip("Test requires fastcore")(test_case)
49+
else:
50+
return test_case
51+
52+
53+
def fake_dataloaders(a=2, b=3, bs=16, n=10):
54+
def get_data(n):
55+
x = torch.randn(bs * n, 1)
56+
return torch.cat((x, a * x + b + 0.1 * torch.randn(bs * n, 1)), 1)
57+
58+
ds = get_data(n)
59+
dblock = DataBlock()
60+
return dblock.dataloaders(ds)
61+
62+
63+
if is_fastai_available():
64+
dummy_model = synth_learner(data=fake_dataloaders())
65+
dummy_config = dict(test="test_0")
66+
else:
67+
dummy_model = None
68+
dummy_config = None
69+
70+
71+
@require_fastai_fastcore
72+
class TestFastaiUtils(TestCase):
73+
@classmethod
74+
def setUpClass(cls):
75+
"""
76+
Share this valid token in all tests below.
77+
"""
78+
cls._api = HfApi(endpoint=ENDPOINT_STAGING)
79+
cls._token = TOKEN
80+
cls._api.set_access_token(TOKEN)
81+
82+
def tearDown(self) -> None:
83+
try:
84+
shutil.rmtree(WORKING_REPO_DIR, onerror=set_write_permission_and_retry)
85+
except FileNotFoundError:
86+
pass
87+
88+
def test_save_pretrained_without_config(self):
89+
REPO_NAME = repo_name("save")
90+
_save_pretrained_fastai(dummy_model, f"{WORKING_REPO_DIR}/{REPO_NAME}")
91+
files = os.listdir(f"{WORKING_REPO_DIR}/{REPO_NAME}")
92+
self.assertTrue("model.pkl" in files)
93+
self.assertTrue("pyproject.toml" in files)
94+
self.assertTrue("README.md" in files)
95+
self.assertEqual(len(files), 3)
96+
97+
def test_save_pretrained_with_config(self):
98+
REPO_NAME = repo_name("save")
99+
_save_pretrained_fastai(
100+
dummy_model, f"{WORKING_REPO_DIR}/{REPO_NAME}", config=dummy_config
101+
)
102+
files = os.listdir(f"{WORKING_REPO_DIR}/{REPO_NAME}")
103+
self.assertTrue("config.json" in files)
104+
self.assertEqual(len(files), 4)
105+
106+
def test_push_to_hub_and_from_pretrained_fastai(self):
107+
REPO_NAME = repo_name("push_to_hub")
108+
push_to_hub_fastai(
109+
learner=dummy_model,
110+
repo_id=f"{USER}/{REPO_NAME}",
111+
token=self._token,
112+
config=dummy_config,
113+
)
114+
model_info = self._api.model_info(
115+
f"{USER}/{REPO_NAME}",
116+
)
117+
self.assertEqual(model_info.modelId, f"{USER}/{REPO_NAME}")
118+
119+
loaded_model = from_pretrained_fastai(f"{USER}/{REPO_NAME}")
120+
self.assertEqual(
121+
dummy_model.show_training_loop(), loaded_model.show_training_loop()
122+
)
123+
self._api.delete_repo(repo_id=f"{REPO_NAME}", token=self._token)

0 commit comments

Comments
 (0)
Please sign in to comment.