-
Notifications
You must be signed in to change notification settings - Fork 706
/
Copy pathcloud.py
165 lines (140 loc) · 6.37 KB
/
cloud.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
from __future__ import annotations
import json, os, pathlib, shutil, subprocess, typing
import typer
from openllm.analytic import OpenLLMTyper
from openllm.accelerator_spec import ACCELERATOR_SPECS
from openllm.common import INTERACTIVE, BentoInfo, DeploymentTarget, EnvVars, output, run_command
app = OpenLLMTyper()
def resolve_cloud_config() -> pathlib.Path:
env = os.environ.get('BENTOML_HOME')
if env is not None:
return pathlib.Path(env) / '.yatai.yaml'
return pathlib.Path.home() / 'bentoml' / '.yatai.yaml'
def _get_deploy_cmd(
bento: BentoInfo, target: typing.Optional[DeploymentTarget] = None, cli_envs: typing.Optional[list[str]] = None
) -> tuple[list[str], EnvVars]:
cmd = ['bentoml', 'deploy', bento.bentoml_tag]
env = EnvVars({'BENTOML_HOME': f'{bento.repo.path}/bentoml'})
# Process CLI env vars first to determine overrides
explicit_envs: dict[str, str] = {}
if cli_envs:
for env_var in cli_envs:
if '=' in env_var:
name, value = env_var.split('=', 1)
explicit_envs[name] = value
else:
name = env_var
value = typing.cast(str, os.environ.get(name))
if value is None:
output(f'Environment variable \'{name}\' specified via --env but not found in the current environment.', style='red')
raise typer.Exit(1)
explicit_envs[name] = value
# Process envs defined in bento.yaml, skipping those overridden by CLI
required_envs = bento.bento_yaml.get('envs', [])
required_env_names = [env['name'] for env in required_envs if 'name' in env and env['name'] not in explicit_envs]
if required_env_names:
output(
f'This model requires the following environment variables to run (unless overridden via --env): {required_env_names!r}',
style='yellow',
)
for env_info in required_envs:
name = typing.cast(str, env_info.get('name'))
if not name or name in explicit_envs:
continue
if os.environ.get(name):
default = os.environ[name]
elif 'value' in env_info:
default = env_info['value']
else:
default = ''
if INTERACTIVE.get():
import questionary
value = questionary.text(f'{name}: (from bento.yaml)', default=default).ask()
else:
if default == '':
output(f'Environment variable {name} (from bento.yaml) is required but not provided', style='red')
raise typer.Exit(1)
else:
value = default
if value is None:
raise typer.Exit(1)
cmd += ['--env', f'{name}={value}']
# Add explicitly provided env vars from CLI
for name, value in explicit_envs.items():
cmd += ['--env', f'{name}={value}']
if target:
cmd += ['--instance-type', target.name]
base_config = resolve_cloud_config()
if not base_config.exists():
raise Exception('Cannot find cloud config.')
# remove before copy
if (bento.repo.path / 'bentoml' / '.yatai.yaml').exists():
(bento.repo.path / 'bentoml' / '.yatai.yaml').unlink()
shutil.copy(base_config, bento.repo.path / 'bentoml' / '.yatai.yaml')
return cmd, env
def ensure_cloud_context() -> None:
import questionary
cmd = ['bentoml', 'cloud', 'current-context']
try:
result = subprocess.check_output(cmd, stderr=subprocess.DEVNULL)
context = json.loads(result)
output(f' bentoml already logged in: {context["endpoint"]}', style='green', level=20)
except subprocess.CalledProcessError:
output(' bentoml not logged in', style='red')
if not INTERACTIVE.get():
output('\n get bentoml logged in by:')
output(' $ bentoml cloud login', style='orange')
output('')
output(
""" * you may need to visit https://cloud.bentoml.com to get an account. you can also bring your own bentoml cluster (BYOC) to your team from https://bentoml.com/contact""",
style='yellow',
)
raise typer.Exit(1)
else:
action = questionary.select(
'Choose an action:', choices=['I have a BentoCloud account', 'get an account in two minutes']
).ask()
if action is None:
raise typer.Exit(1)
elif action == 'get an account in two minutes':
output('Please visit https://cloud.bentoml.com to get your token', style='yellow')
endpoint = questionary.text('Enter the endpoint: (similar to https://my-org.cloud.bentoml.com)').ask()
if endpoint is None:
raise typer.Exit(1)
token = questionary.text('Enter your token: (similar to cniluaxxxxxxxx)').ask()
if token is None:
raise typer.Exit(1)
cmd = ['bentoml', 'cloud', 'login', '--api-token', token, '--endpoint', endpoint]
try:
result = subprocess.check_output(cmd)
output(' Logged in successfully', style='green')
except subprocess.CalledProcessError:
output(' Failed to login', style='red')
raise typer.Exit(1)
def get_cloud_machine_spec() -> list[DeploymentTarget]:
ensure_cloud_context()
cmd = ['bentoml', 'deployment', 'list-instance-types', '-o', 'json']
try:
result = subprocess.check_output(cmd, stderr=subprocess.DEVNULL)
instance_types = json.loads(result)
return [
DeploymentTarget(
source='cloud',
name=it['name'],
price=it['price'],
platform='linux',
accelerators=(
[ACCELERATOR_SPECS[it['gpu_type']] for _ in range(int(it['gpu']))]
if it.get('gpu') and it['gpu_type'] in ACCELERATOR_SPECS
else []
),
)
for it in instance_types
]
except (subprocess.CalledProcessError, json.JSONDecodeError):
output('Failed to get cloud instance types', style='red')
return []
def deploy(bento: BentoInfo, target: DeploymentTarget, cli_envs: typing.Optional[list[str]] = None) -> None:
ensure_cloud_context()
cmd, env = _get_deploy_cmd(bento, target, cli_envs=cli_envs)
run_command(cmd, env=env, cwd=None)