model.py 3.47 KB
Newer Older
Bruce MacDonald's avatar
Bruce MacDonald committed
1
2
import os
import requests
Bruce MacDonald's avatar
Bruce MacDonald committed
3
import validators
Bruce MacDonald's avatar
Bruce MacDonald committed
4
5
from urllib.parse import urlsplit, urlunsplit
from tqdm import tqdm
Michael Yang's avatar
Michael Yang committed
6
7


8
9
10
11
12
models_endpoint_url = 'https://ollama.ai/api/models'


def models(models_home='.', *args, **kwargs):
    for _, _, files in os.walk(models_home):
Michael Yang's avatar
Michael Yang committed
13
        for file in files:
Bruce MacDonald's avatar
Bruce MacDonald committed
14
            base, ext = os.path.splitext(file)
15
16
            if ext == '.bin':
                yield base
Bruce MacDonald's avatar
Bruce MacDonald committed
17
18


19
def pull(model, models_home='.', *args, **kwargs):
Bruce MacDonald's avatar
Bruce MacDonald committed
20
    url = model
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
    if not validators.url(url) and not url.startswith('huggingface.co'):
        # this may just be a local model location
        if model in models(models_home):
            return model
        # see if we have this model in our directory
        response = requests.get(models_endpoint_url)
        response.raise_for_status()
        directory = response.json()
        for model_info in directory:
            if model_info.get('name') == model:
                url = f"https://{model_info.get('url')}"
                break
        if not validators.url(url):
            raise Exception(f'Unknown model {model}')

    if not (url.startswith('http://') or url.startswith('https://')):
        url = f'https://{url}'
Bruce MacDonald's avatar
Bruce MacDonald committed
38

Bruce MacDonald's avatar
Bruce MacDonald committed
39
    parts = urlsplit(url)
40
    path_parts = parts.path.split('/tree/')
Bruce MacDonald's avatar
Bruce MacDonald committed
41
42

    if len(path_parts) == 1:
43
44
        location = path_parts[0]
        branch = 'main'
Bruce MacDonald's avatar
Bruce MacDonald committed
45
    else:
46
        location, branch = path_parts
Bruce MacDonald's avatar
Bruce MacDonald committed
47

48
    location = location.strip('/')
Bruce MacDonald's avatar
Bruce MacDonald committed
49
50

    # Reconstruct the URL
51
    download_url = urlunsplit(
Bruce MacDonald's avatar
Bruce MacDonald committed
52
        (
53
            'https',
Bruce MacDonald's avatar
Bruce MacDonald committed
54
            parts.netloc,
55
            f'/api/models/{location}/tree/{branch}',
Bruce MacDonald's avatar
Bruce MacDonald committed
56
57
58
59
60
            parts.query,
            parts.fragment,
        )
    )

61
    response = requests.get(download_url)
Bruce MacDonald's avatar
Bruce MacDonald committed
62
63
64
65
    response.raise_for_status()  # Raises stored HTTPError, if one occurred

    json_response = response.json()

Bruce MacDonald's avatar
Bruce MacDonald committed
66
67
    # get the last bin file we find, this is probably the most up to date
    download_url = None
Bruce MacDonald's avatar
Bruce MacDonald committed
68
    file_size = 0
Bruce MacDonald's avatar
Bruce MacDonald committed
69
    for file_info in json_response:
70
71
72
73
74
75
        if file_info.get('type') == 'file' and file_info.get('path').endswith('.bin'):
            f_path = file_info.get('path')
            download_url = (
                f'https://huggingface.co/{location}/resolve/{branch}/{f_path}'
            )
            file_size = file_info.get('size')
Bruce MacDonald's avatar
Bruce MacDonald committed
76

Bruce MacDonald's avatar
Bruce MacDonald committed
77
    if download_url is None:
78
        raise Exception('No model found')
Bruce MacDonald's avatar
Bruce MacDonald committed
79

80
    local_filename = os.path.join(models_home, os.path.basename(url)) + '.bin'
Bruce MacDonald's avatar
Bruce MacDonald committed
81

Bruce MacDonald's avatar
Bruce MacDonald committed
82
    # Check if file already exists
Bruce MacDonald's avatar
Bruce MacDonald committed
83
    first_byte = 0
Bruce MacDonald's avatar
Bruce MacDonald committed
84
85
86
    if os.path.exists(local_filename):
        # TODO: check if the file is the same SHA
        first_byte = os.path.getsize(local_filename)
Bruce MacDonald's avatar
Bruce MacDonald committed
87
88
89
90

    if first_byte >= file_size:
        return local_filename

91
    print(f'Pulling {model}...')
Bruce MacDonald's avatar
Bruce MacDonald committed
92
93
94

    # If file size is non-zero, resume download
    if first_byte != 0:
95
        header = {'Range': f'bytes={first_byte}-'}
Bruce MacDonald's avatar
Bruce MacDonald committed
96
97
    else:
        header = {}
Bruce MacDonald's avatar
Bruce MacDonald committed
98

Bruce MacDonald's avatar
Bruce MacDonald committed
99
100
    response = requests.get(download_url, headers=header, stream=True)
    response.raise_for_status()  # Raises stored HTTPError, if one occurred
Bruce MacDonald's avatar
Bruce MacDonald committed
101

102
    total_size = int(response.headers.get('content-length', 0))
Bruce MacDonald's avatar
Bruce MacDonald committed
103

104
    with open(local_filename, 'ab' if first_byte else 'wb') as file, tqdm(
Bruce MacDonald's avatar
Bruce MacDonald committed
105
        total=total_size,
106
        unit='iB',
Bruce MacDonald's avatar
Bruce MacDonald committed
107
108
109
        unit_scale=True,
        unit_divisor=1024,
        initial=first_byte,
110
111
        ascii=' ==',
        bar_format='Downloading [{bar}] {percentage:3.2f}% {rate_fmt}{postfix}',
Bruce MacDonald's avatar
Bruce MacDonald committed
112
113
114
115
    ) as bar:
        for data in response.iter_content(chunk_size=1024):
            size = file.write(data)
            bar.update(size)
Bruce MacDonald's avatar
Bruce MacDonald committed
116
117

    return local_filename