Skip to content

Commit 6c31a4c

Browse files
authored
feat(ray): unify cli commands (#167)
Because - Current DX of custom model development is not user friendly This commit - have a unified entry command `instill` for all operation - remove the repo field in instill.yaml, which currently used for {namespace}/{model-id} - have `name` as positional field for each command, format is {namespace}/{model-id}
1 parent 8cddc58 commit 6c31a4c

File tree

9 files changed

+338
-206
lines changed

9 files changed

+338
-206
lines changed

β€Žinstill/helpers/build.pyβ€Ž

Lines changed: 0 additions & 144 deletions
This file was deleted.

β€Žinstill/helpers/cli.pyβ€Ž

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
import argparse
2+
3+
# import hashlib
4+
import os
5+
import platform
6+
import shutil
7+
import subprocess
8+
import tempfile
9+
10+
import ray
11+
import yaml
12+
13+
import instill
14+
from instill.helpers.const import DEFAULT_DEPENDENCIES
15+
from instill.helpers.errors import ModelConfigException
16+
from instill.utils.logger import Logger
17+
18+
19+
def config_check_required_fields(c):
20+
if "build" not in c or c["build"] is None:
21+
raise ModelConfigException("build")
22+
if "gpu" not in c["build"] or c["build"]["gpu"] is None:
23+
raise ModelConfigException("gpu")
24+
if "python_version" not in c["build"] or c["build"]["python_version"] is None:
25+
raise ModelConfigException("python_version")
26+
27+
28+
def cli():
29+
if platform.machine() in ("i386", "AMD64", "x86_64"):
30+
default_platform = "amd64"
31+
else:
32+
default_platform = platform.machine()
33+
parser = argparse.ArgumentParser()
34+
subcommands = parser.add_subparsers(required=True)
35+
36+
# init
37+
init_parser = subcommands.add_parser("init", help="Initialize model directory")
38+
init_parser.set_defaults(func=init)
39+
40+
# build
41+
build_parser = subcommands.add_parser("build", help="Build model image")
42+
build_parser.set_defaults(func=build)
43+
build_parser.add_argument(
44+
"name",
45+
help="user and model namespace, in the format of <user-id>/<model-id>",
46+
)
47+
build_parser.add_argument(
48+
"-t",
49+
"--tag",
50+
help="tag for the model image, default to `latest`",
51+
# default=hashlib.sha256().hexdigest(),
52+
default="latest",
53+
required=False,
54+
)
55+
build_parser.add_argument(
56+
"--no-cache",
57+
help="build the image without cache",
58+
action="store_true",
59+
required=False,
60+
)
61+
build_parser.add_argument(
62+
"--target-arch",
63+
help="target platform architecture for the model image, default to host",
64+
default=default_platform,
65+
choices=["arm64", "amd64"],
66+
required=False,
67+
)
68+
69+
# push
70+
push_parser = subcommands.add_parser("push", help="Push model image")
71+
push_parser.set_defaults(func=push)
72+
push_parser.add_argument(
73+
"name",
74+
help="user and model namespace, in the format of <user-id>/<model-id>",
75+
)
76+
push_parser.add_argument(
77+
"-u",
78+
"--url",
79+
help="image registry url, in the format of host:port, default to api.instill.tech",
80+
default="api.instill.tech",
81+
required=False,
82+
)
83+
push_parser.add_argument(
84+
"-t",
85+
"--tag",
86+
help="tag for the model image, default to `latest`",
87+
default="latest",
88+
required=False,
89+
)
90+
91+
args = parser.parse_args()
92+
args.func(args)
93+
94+
95+
def init(_):
96+
shutil.copyfile(
97+
__file__.replace("cli.py", "init-templates/instill.yaml"),
98+
f"{os.getcwd()}/instill.yaml",
99+
)
100+
shutil.copyfile(
101+
__file__.replace("cli.py", "init-templates/model.py"),
102+
f"{os.getcwd()}/model.py",
103+
)
104+
shutil.copyfile(
105+
__file__.replace("cli.py", "init-templates/.dockerignore"),
106+
f"{os.getcwd()}/.dockerignore",
107+
)
108+
109+
110+
def build(args):
111+
try:
112+
Logger.i("[Instill Builder] Loading config file...")
113+
with open("instill.yaml", "r", encoding="utf8") as f:
114+
Logger.i("[Instill Builder] Parsing config file...")
115+
config = yaml.safe_load(f)
116+
117+
config_check_required_fields(config)
118+
119+
build_params = config["build"]
120+
121+
python_version = build_params["python_version"].replace(".", "")
122+
ray_version = ray.__version__
123+
instill_version = instill.__version__
124+
125+
if not build_params["gpu"]:
126+
cuda_suffix = ""
127+
elif (
128+
"cuda_version" in build_params and not build_params["cuda_version"] is None
129+
):
130+
cuda_suffix = f'-cu{build_params["cuda_version"].replace(".", "")}'
131+
else:
132+
cuda_suffix = "-gpu"
133+
134+
system_str = ""
135+
if (
136+
"system_packages" in build_params
137+
and not build_params["system_packages"] is None
138+
):
139+
for p in build_params["system_packages"]:
140+
system_str += p + " "
141+
142+
packages_str = ""
143+
if (
144+
"python_packages" in build_params
145+
and not build_params["python_packages"] is None
146+
):
147+
for p in build_params["python_packages"]:
148+
packages_str += p + " "
149+
for p in DEFAULT_DEPENDENCIES:
150+
packages_str += p + " "
151+
packages_str += f"instill-sdk=={instill_version}"
152+
153+
with tempfile.TemporaryDirectory() as tmpdir:
154+
shutil.copyfile(
155+
__file__.replace("cli.py", "init-templates/Dockerfile"),
156+
f"{tmpdir}/Dockerfile",
157+
)
158+
shutil.copytree(os.getcwd(), tmpdir, dirs_exist_ok=True)
159+
160+
target_arch_suffix = "-aarch64" if args.target_arch == "arm64" else ""
161+
162+
Logger.i("[Instill Builder] Building model image...")
163+
command = [
164+
"docker",
165+
"buildx",
166+
"build",
167+
"--build-arg",
168+
f"TARGET_ARCH_SUFFIX={target_arch_suffix}",
169+
"--build-arg",
170+
f"RAY_VERSION={ray_version}",
171+
"--build-arg",
172+
f"PYTHON_VERSION={python_version}",
173+
"--build-arg",
174+
f"CUDA_SUFFIX={cuda_suffix}",
175+
"--build-arg",
176+
f"PACKAGES={packages_str}",
177+
"--build-arg",
178+
f"SYSTEM_PACKAGES={system_str}",
179+
"--platform",
180+
f"linux/{args.target_arch}",
181+
"-t",
182+
f"{args.name}:{args.tag}",
183+
tmpdir,
184+
"--load",
185+
]
186+
if args.no_cache:
187+
command.append("--no-cache")
188+
subprocess.run(
189+
command,
190+
check=True,
191+
)
192+
Logger.i(f"[Instill Builder] {args.name}:{args.tag} built")
193+
except subprocess.CalledProcessError:
194+
Logger.e("[Instill Builder] Build failed")
195+
except Exception as e:
196+
Logger.e("[Instill Builder] Prepare failed")
197+
Logger.e(e)
198+
finally:
199+
Logger.i("[Instill Builder] Done")
200+
201+
202+
def push(args):
203+
try:
204+
registry = args.url
205+
206+
subprocess.run(
207+
[
208+
"docker",
209+
"tag",
210+
f"{args.name}:{args.tag}",
211+
f"{registry}/{args.name}:{args.tag}",
212+
],
213+
check=True,
214+
)
215+
Logger.i("[Instill Builder] Pushing model image...")
216+
subprocess.run(
217+
["docker", "push", f"{registry}/{args.name}:{args.tag}"], check=True
218+
)
219+
Logger.i(f"[Instill Builder] {registry}/{args.name}:{args.tag} pushed")
220+
except subprocess.CalledProcessError:
221+
Logger.e("[Instill Builder] Push failed")
222+
except Exception as e:
223+
Logger.e("[Instill Builder] Prepare failed")
224+
Logger.e(e)
225+
finally:
226+
Logger.i("[Instill Builder] Done")
227+
228+
229+
if __name__ == "__main__":
230+
cli()
File renamed without changes.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
build:
2+
# set to true if your model requires GPU
3+
gpu: true
4+
5+
# python version, currently only support 3.11
6+
python_version: "3.11"
7+
8+
# cuda version if `gpu` is set to true
9+
# support 11.5, 11.6 ,11.7 ,11.7 ,12.1
10+
cuda_version: "12.1"
11+
12+
# a list of python packages in the format of {package-name}=={version}
13+
# python_packages:
14+
# - torch==2.3.1
15+
# - transformers==4.41.2
16+
17+
# a list of system packages from apt package manager
18+
# system_packages:
19+
# - htop
20+
# - libglib2.0-0

0 commit comments

Comments
Β (0)