Skip to content

Commit f5ddb42

Browse files
author
思凡 叶
committed
Darwin Kit
1 parent 9c74538 commit f5ddb42

File tree

268 files changed

+19003
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

268 files changed

+19003
-0
lines changed

MANIFEST.in

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
include README.md
2+
include README.zh.md
3+
include LICENSE
4+
recursive-include LICENSES *
5+
recursive-include darkit *.cu
6+
recursive-include darkit *.cpp
7+
recursive-include darkit/core/web/build *
8+
recursive-include darkit/tmp *

darkit/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
__version__ = "0.1.10"

darkit/cli/__init__.py

Whitespace-only changes.

darkit/cli/main.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import os
2+
import signal
3+
import click
4+
from darkit import __version__
5+
from .src.pid import read_pid, save_pid, remove_pid_file
6+
7+
8+
@click.group()
9+
@click.version_option(version=__version__, prog_name="DarwinKit")
10+
def cli():
11+
pass
12+
13+
14+
try:
15+
from darkit.lm.command import command as lm_command
16+
17+
cli.add_command(lm_command)
18+
except ImportError as e:
19+
print("lm command module not found", e)
20+
21+
22+
@cli.command("start")
23+
@click.option("--port", type=int, default=8000, help="Web 服务端口")
24+
@click.option("--daemon", "-D", is_flag=True, help="是否以守护进程启动")
25+
def start_server(port: int, daemon: bool):
26+
"""
27+
开启 WEB 服务
28+
"""
29+
from darkit.core.utils.server import start_uvicorn
30+
31+
if read_pid():
32+
click.echo("服务已在运行。")
33+
return
34+
35+
p = start_uvicorn(port, daemon)
36+
if daemon:
37+
p.start()
38+
save_pid(p.pid)
39+
print(f"服务已在后台以守护进程启动,端口: {port}")
40+
os._exit(0)
41+
else:
42+
print(f"服务已启动,端口: {port}")
43+
p.start()
44+
p.join()
45+
46+
47+
@cli.command("stop")
48+
def stop_server():
49+
"""
50+
停止 WEB 服务
51+
"""
52+
pid = read_pid()
53+
if pid is None:
54+
click.echo("服务未在运行或 PID 文件不存在。")
55+
return
56+
57+
try:
58+
os.kill(pid, signal.SIGTERM) # 发送终止信号
59+
remove_pid_file() # 移除 PID 文件
60+
click.echo(f"服务已停止,PID: {pid}")
61+
except ProcessLookupError:
62+
click.echo(f"没有找到 PID 为 {pid} 的进程。")
63+
remove_pid_file() # 移除 PID 文件以防错误
64+
except Exception as e:
65+
click.echo(f"停止服务时发生错误: {e}")
66+
67+
68+
if __name__ == "__main__":
69+
cli()

darkit/cli/src/__init__.py

Whitespace-only changes.

darkit/cli/src/decorator.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import click
2+
from dataclasses import MISSING
3+
from typing import Callable
4+
5+
6+
def dataclass_options(options: dict, default=None) -> Callable:
7+
def decorator(func: Callable) -> Callable:
8+
# 自动生成 click 选项
9+
commands = []
10+
for name, field in options.items():
11+
option_default = default.__dict__[name] if default else field["default"]
12+
if option_default is MISSING:
13+
option_default = None
14+
15+
if isinstance(field["type"], list):
16+
option_type = click.Choice(field["type"])
17+
else:
18+
option_type = eval(field["type"])
19+
20+
# 添加 click 选项
21+
commands.append(
22+
click.option(
23+
f"--{name}",
24+
default=option_default,
25+
type=option_type,
26+
show_default=True,
27+
)
28+
)
29+
30+
def wrapped_command(*args, **kwargs):
31+
nonlocal func
32+
# 获取 kwargs 中存在与 _fields 的参数
33+
relevant_kwargs = {
34+
k: v
35+
for k, v in kwargs.items()
36+
if k in [name for name, _ in options.items()] and v is not None
37+
}
38+
39+
# 把 config 添加到 args 的最后
40+
args = args + (relevant_kwargs,)
41+
return func(*args, **kwargs)
42+
43+
# 保存原始函数的参数
44+
if hasattr(func, "__doc__"):
45+
wrapped_command.__doc__ = func.__doc__
46+
if hasattr(func, "__click_params__"):
47+
wrapped_command.__click_params__ = func.__click_params__
48+
49+
for option in commands:
50+
wrapped_command = option(wrapped_command)
51+
52+
return wrapped_command
53+
54+
return decorator

darkit/cli/src/generate.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
CLI_NAME = "darkit"
2+
3+
4+
def dict_to_cmd_args(d: dict) -> str:
5+
return " ".join([f"--{k} {v}" for k, v in d.items() if v not in [None, ""]])
6+
7+
8+
def gen_train_command(type: str, model: str, mconf: dict, tconf: dict):
9+
mconf_args = dict_to_cmd_args(mconf)
10+
tconf_args = dict_to_cmd_args(tconf)
11+
return f"{CLI_NAME} {type} train {model} {mconf_args} {tconf_args}"

darkit/cli/src/options.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
from dataclasses import is_dataclass, fields, MISSING
2+
from enum import Enum
3+
from typing import Type, Literal, Optional, Union, get_origin, get_args
4+
5+
6+
def get_option_definer(conf_cls: Type, conf_comment: dict):
7+
"""
8+
生成配置选项字典, 将 py 数据类型转换为 json 可序列化的数据类型。
9+
10+
Args:
11+
conf_cls (Type): 需要是 dataclass 或者有 to_options 方法。to_options 方法返回一个字典,结构与 options 相同。
12+
conf_comment (dict): 配置字段的注释信息。
13+
14+
Returns:
15+
dict: 配置选项字典。
16+
17+
Raises:
18+
ValueError: 如果 conf_cls 不是 dataclass 且没有 to_options 方法。
19+
"""
20+
has_method = hasattr(conf_cls, "to_options") and callable(
21+
getattr(conf_cls, "to_options")
22+
)
23+
if has_method:
24+
return conf_cls.to_options()
25+
elif is_dataclass(conf_cls):
26+
_fields = fields(conf_cls)
27+
28+
options = dict()
29+
30+
for field in _fields:
31+
option_default = field.default
32+
if option_default is MISSING:
33+
option_default = None
34+
35+
option_type = field.type
36+
required = True
37+
if field.name == "device":
38+
option_type = Literal["cuda", "cpu"]
39+
if isinstance(option_type, type):
40+
if issubclass(option_type, Enum): # type: ignore
41+
all_values = [option.value for option in option_type]
42+
option_type = all_values
43+
else:
44+
option_type = option_type.__name__
45+
elif get_origin(option_type) is Union:
46+
option_type = "str"
47+
elif get_origin(option_type) is Literal:
48+
option_type = get_args(option_type)
49+
elif get_origin(option_type) is Optional:
50+
required = False
51+
option_type = get_args(option_type)[0]
52+
else:
53+
required = False
54+
option_type = "str"
55+
56+
comment_dict = conf_comment.get(field.name, {})
57+
description = comment_dict.get("description")
58+
range = comment_dict.get("range")
59+
comment = f"{description} {range}" if description else None
60+
options[field.name] = {
61+
"default": option_default,
62+
"type": option_type,
63+
"required": required,
64+
"comment": comment,
65+
}
66+
67+
return options
68+
else:
69+
raise ValueError(
70+
f"{conf_cls.__name__} should be a dataclass or have a to_options method."
71+
)

darkit/cli/src/pid.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import os
2+
from pathlib import Path
3+
from darkit.core.utils import DSPIKE_LLM_HOME
4+
5+
PID_FILE = Path(os.path.expanduser(DSPIKE_LLM_HOME)) / ".server.pid"
6+
7+
8+
def save_pid(pid):
9+
with open(PID_FILE, "w") as f:
10+
f.write(str(pid))
11+
12+
13+
def read_pid():
14+
try:
15+
with open(PID_FILE, "r") as f:
16+
return int(f.read().strip())
17+
except FileNotFoundError:
18+
return None
19+
20+
21+
def remove_pid_file():
22+
if os.path.exists(PID_FILE):
23+
os.remove(PID_FILE)

darkit/core/README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# DarwinKit Server
2+
3+
## Develop
4+
- Start the fastapi server
5+
```bash
6+
uvicorn darkit.server.main:app --reload --host 0.0.0.0
7+
```
8+
- Start the svelte web
9+
```bash
10+
cd web
11+
npm run dev
12+
```

0 commit comments

Comments
 (0)