ftllm 0.0.0.8__py3-none-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
ftllm/__init__.py ADDED
@@ -0,0 +1,4 @@
1
+ __all__ = ["llm"]
2
+
3
+ from importlib.metadata import version
4
+ __version__ = version("ftllm") # 从安装的元数据读取
ftllm/chat.py ADDED
@@ -0,0 +1,56 @@
1
+ import argparse
2
+ from .util import make_normal_parser
3
+ import readline
4
+
5
+ def args_parser():
6
+ parser = make_normal_parser('fastllm_chat')
7
+ args = parser.parse_args()
8
+ return args
9
+
10
+ def fastllm_chat(args):
11
+ from .util import make_normal_llm_model
12
+ model = make_normal_llm_model(args)
13
+
14
+ generation_config = {
15
+ 'repetition_penalty': 1.0,
16
+ 'top_p': 0.8,
17
+ 'top_k': 1,
18
+ 'temperature': 1.0
19
+ }
20
+ import os
21
+ import json
22
+ if (os.path.exists(os.path.join(args.path, "generation_config.json"))):
23
+ with open(os.path.join(args.path, "generation_config.json"), "r", encoding="utf-8") as file:
24
+ config = json.load(file)
25
+ if ('do_sample' in config and config['do_sample']):
26
+ for it in ["repetition_penalty", "top_p", "top_k", "temperature"]:
27
+ if (it in config):
28
+ generation_config[it] = config[it];
29
+
30
+ hint = "输入内容开始对话\n'clear'清空记录\n'stop'终止程序."
31
+ history = []
32
+
33
+ print(hint)
34
+ while True:
35
+ query = input("\nUser:")
36
+ if query.strip() == "stop":
37
+ break
38
+ if query.strip() == "clear":
39
+ history = []
40
+ print(hint)
41
+ continue
42
+ print("AI:", end = "");
43
+ curResponse = "";
44
+ for response in model.stream_response(query, history = history,
45
+ repeat_penalty = generation_config["repetition_penalty"],
46
+ top_p = generation_config["top_p"],
47
+ top_k = generation_config["top_k"],
48
+ temperature = generation_config["temperature"]):
49
+ curResponse += response;
50
+ print(response, flush = True, end = "")
51
+ history.append((query, curResponse))
52
+ model.release_memory()
53
+
54
+ if __name__ == "__main__":
55
+ args = args_parser()
56
+ fastllm_chat(args)
ftllm/cli.py ADDED
@@ -0,0 +1,141 @@
1
+ import argparse
2
+ from .util import make_normal_parser
3
+ import readline
4
+ import os
5
+
6
+ def save_defaults_to_json(parser, filename):
7
+ # 获取所有参数的默认值
8
+ defaults = {}
9
+
10
+ # 添加位置参数的默认值
11
+ for action in parser._actions:
12
+ if not action.option_strings and action.dest != 'help':
13
+ defaults[action.dest] = action.default if action.default is not None else ""
14
+ if defaults["model"] == '':
15
+ defaults["model"] = "Qwen/Qwen2-0.5B-Instruct"
16
+ for action in parser._actions:
17
+ # 跳过位置参数(没有option_strings的)
18
+ if not action.option_strings:
19
+ continue
20
+
21
+ # 获取参数名(选择最长的选项名,去掉前面的--或-)
22
+ name = max(action.option_strings, key=len).lstrip('-')
23
+ if (name == 'low' or name == 'path'):
24
+ continue
25
+ if (name == 'device'):
26
+ action.default = ""
27
+ # 处理store_true类型的参数
28
+ if action.const is True:
29
+ defaults[name] = False # store_true参数的默认值是False
30
+ else:
31
+ defaults[name] = action.default
32
+ defaults["FASTLLM_USE_NUMA"] = "OFF"
33
+ defaults["FASTLLM_NUMA_THREADS"] = 27
34
+
35
+ # 将字典转换为JSON并保存到文件
36
+ import json
37
+ with open(filename, 'w', encoding='utf-8') as f:
38
+ json.dump(defaults, f, indent = 4, ensure_ascii = False)
39
+
40
+ print("Create config to -> \"" + filename + "\"")
41
+
42
+ def args_parser():
43
+ parser = argparse.ArgumentParser(description = "fastllm")
44
+ subparsers = parser.add_subparsers(dest='command', help='子命令')
45
+
46
+ # 创建共享的解析器
47
+ shared_parser = make_normal_parser("fastllm", add_help = False)
48
+
49
+ # 下载解析器
50
+ from ftllm.download import make_download_parser
51
+ download_parser = make_download_parser(add_help = False)
52
+
53
+ # 打开ui界面
54
+ ui_parser_ = subparsers.add_parser('ui', parents = [shared_parser], help = 'ui模式')
55
+
56
+ # 创建chat子命令(使用共享解析器)
57
+ chat_parser_ = subparsers.add_parser('chat', parents = [shared_parser], help = '聊天模式')
58
+
59
+ # 创建run子命令(使用相同的共享解析器)
60
+ run_parser_ = subparsers.add_parser('run', parents = [shared_parser], help = '运行模式')
61
+
62
+ download_parser_ = subparsers.add_parser('download', parents = [download_parser], help = '下载模型')
63
+
64
+ # 创建webui子命令(独立的解析器)
65
+ webui_parser_ = subparsers.add_parser('webui', parents = [shared_parser], help='Web UI')
66
+ webui_parser_.add_argument('--port', type = int, default = 1616, help = '端口号')
67
+
68
+ server_parser = shared_parser
69
+ from ftllm.util import add_server_args
70
+ add_server_args(server_parser)
71
+ server_parser_ = subparsers.add_parser('serve', parents = [server_parser], help = 'api模式')
72
+ serve_parser_ = subparsers.add_parser('server', parents = [server_parser], help = 'api模式')
73
+
74
+ config_parser_ = subparsers.add_parser('config', help = '创建配置文件')
75
+ config_parser_.add_argument('file', nargs='?', help = '配置文件的路径')
76
+
77
+ parser.add_argument('-v', '--version', action='store_true', help='输出版本号并退出')
78
+
79
+ return parser
80
+
81
+ def main():
82
+ args = args_parser().parse_args()
83
+ if (args.version):
84
+ from . import __version__
85
+ print("ftllm version: " + __version__)
86
+ return
87
+ # 根据不同的子命令执行不同的操作
88
+ if args.command == 'ui':
89
+ from .ui import FastllmStartUI
90
+ FastllmStartUI()
91
+ if args.command == 'config':
92
+ file = args.file
93
+ if not(file) or file == '':
94
+ file = "config.json"
95
+ if os.path.exists(file):
96
+ choice = input("File '" + file + "' exists,replace it? (Y/N): ").strip().upper()
97
+ if choice == 'Y':
98
+ pass
99
+ else:
100
+ return
101
+ save_defaults_to_json(make_normal_parser("fastllm", add_help = False), file)
102
+ elif args.command in ('chat', 'run'):
103
+ from ftllm.chat import fastllm_chat
104
+ fastllm_chat(args)
105
+ elif args.command == "download":
106
+ from ftllm.download import HFDDownloader
107
+ HFDDownloader(args).run()
108
+ elif args.command == 'webui':
109
+ current_path = os.path.dirname(os.path.abspath(__file__))
110
+ web_demo_path = os.path.join(current_path, 'web_demo.py')
111
+
112
+ args_dict = vars(args)
113
+ args_dict.pop('command', None)
114
+ port = args_dict.pop('port', 1616)
115
+ model = args_dict.pop('model', '')
116
+ # Convert remaining arguments to command line format
117
+ args_list = []
118
+ if (model != ''):
119
+ args_list.append(model)
120
+ for key, value in args_dict.items():
121
+ if value is not None:
122
+ if isinstance(value, bool):
123
+ if value:
124
+ args_list.append(f"--{key}")
125
+ elif value != '':
126
+ args_list.append(f"--{key} {value}")
127
+
128
+ # Build the command
129
+ cmd = f"streamlit run --server.port {port} {web_demo_path} -- {' '.join(args_list)}"
130
+ print(f"Running: {cmd}")
131
+ os.system(cmd)
132
+ return
133
+ elif args.command in ('server', 'serve'):
134
+ from ftllm.server import fastllm_server
135
+ fastllm_server(args)
136
+ else:
137
+ print("Invalid command: ", args.command)
138
+ exit(0)
139
+
140
+ if __name__ == "__main__":
141
+ main()
ftllm/download.py ADDED
@@ -0,0 +1,282 @@
1
+ #!/usr/bin/env python3
2
+ import os
3
+ import re
4
+ import json
5
+ import argparse
6
+ import subprocess
7
+ import sys
8
+ from pathlib import Path
9
+ from fnmatch import translate
10
+ from typing import List, Tuple
11
+ import requests
12
+ from .util import get_fastllm_cache_path
13
+ from .util import make_download_parser
14
+
15
+ def find_metadata(repo_id) -> bool:
16
+ hf_endpoint = os.environ.get("HF_ENDPOINT", "https://hf-mirror.com")
17
+ url = f"{hf_endpoint}/api/models/{repo_id}"
18
+ try:
19
+ response = requests.get(url)
20
+ response.raise_for_status()
21
+ metadata = response.json()
22
+ return True
23
+ except requests.HTTPError as e:
24
+ return False
25
+
26
+ def search_model(repo_id):
27
+ hf_endpoint = os.environ.get("HF_ENDPOINT", "https://hf-mirror.com")
28
+ url = f"{hf_endpoint}/api/models?search={repo_id}&limit=3"
29
+ try:
30
+ response = requests.get(url)
31
+ response.raise_for_status()
32
+ return response.json()
33
+ except requests.HTTPError as e:
34
+ return []
35
+ # ANSI颜色代码
36
+ COLORS = {
37
+ "RED": "\033[0;31m",
38
+ "GREEN": "\033[0;32m",
39
+ "YELLOW": "\033[1;33m",
40
+ "NC": "\033[0m"
41
+ }
42
+
43
+ def color_print(text: str, color: str) -> None:
44
+ print(f"{COLORS[color]}{text}{COLORS['NC']}", flush=True)
45
+
46
+ class HFDArgs:
47
+ def __init__(self):
48
+ self.repo_id = ""
49
+ self.include = []
50
+ self.exclude = []
51
+ self.hf_username = ""
52
+ self.hf_token = ""
53
+ self.tool = "aria2c"
54
+ self.x = 4
55
+ self.j = 5
56
+ self.dataset = False
57
+ self.local_dir = ""
58
+ self.revision = "main"
59
+
60
+ class HFDDownloader:
61
+ def __init__(self, args):
62
+ self.args = args
63
+ self.validate_arguments()
64
+ self.setup_paths()
65
+ self.hf_endpoint = os.environ.get("HF_ENDPOINT", "https://hf-mirror.com")
66
+
67
+ # 确保缓存目录存在
68
+ (self.local_dir / ".hfd").mkdir(parents=True, exist_ok=True)
69
+
70
+ def validate_arguments(self):
71
+ for attr in ["x", "j"]:
72
+ value = getattr(self.args, attr)
73
+ if not (1 <= value <= 10):
74
+ color_print(f"{attr} must be between 1-10", "RED")
75
+ sys.exit(1)
76
+
77
+ def setup_paths(self):
78
+ # 处理本地目录路径
79
+ if self.args.local_dir:
80
+ self.local_dir = Path(self.args.local_dir).expanduser().resolve()
81
+ else:
82
+ repo_name = self.args.repo_id.split("/")[-1] if "/" in self.args.repo_id else self.args.repo_id
83
+ self.local_dir = Path.cwd() / repo_name
84
+
85
+ self.metadata_file = self.local_dir / ".hfd" / "repo_metadata.json"
86
+ self.command_file = self.local_dir / ".hfd" / "last_command"
87
+ self.fileslist_file = self.local_dir / ".hfd" / f"{self.args.tool}_urls.txt"
88
+
89
+ def run(self):
90
+ try:
91
+ finish_flag = os.path.join(self.local_dir, "FASTLLM_FINISH_FLAG")
92
+ if (os.path.exists(finish_flag)):
93
+ return
94
+ # 获取并处理元数据
95
+ metadata = self.fetch_metadata()
96
+ self.check_authentication(metadata)
97
+
98
+ # 生成下载列表
99
+ if self.should_regenerate_filelist():
100
+ self.generate_filelist(metadata)
101
+
102
+ # 执行下载
103
+ self.execute_download()
104
+
105
+ color_print("Download completed successfully.", "GREEN")
106
+ with open(finish_flag, 'w') as file:
107
+ file.write('finish')
108
+ except KeyboardInterrupt:
109
+ color_print("\nDownload interrupted. You can resume by re-running the command.", "YELLOW")
110
+ sys.exit(1)
111
+ except Exception as e:
112
+ color_print(f"Error: {str(e)}", "RED")
113
+ sys.exit(1)
114
+
115
+ def fetch_metadata(self) -> dict:
116
+ color_print("Fetching repository metadata...", "YELLOW")
117
+
118
+ api_path = "datasets" if self.args.dataset else "models"
119
+ url = f"{self.hf_endpoint}/api/{api_path}/{self.args.repo_id}"
120
+ if self.args.revision != "main":
121
+ url += f"/revision/{self.args.revision}"
122
+
123
+ headers = {"Authorization": f"Bearer {self.args.hf_token}"} if self.args.hf_token else {}
124
+
125
+ try:
126
+ response = requests.get(url, headers=headers)
127
+ response.raise_for_status()
128
+ metadata = response.json()
129
+
130
+ # 保存元数据
131
+ with open(self.metadata_file, "w") as f:
132
+ json.dump(metadata, f)
133
+
134
+ return metadata
135
+ except requests.HTTPError as e:
136
+ color_print(f"Failed to fetch metadata: {e}", "RED")
137
+ sys.exit(1)
138
+
139
+ def check_authentication(self, metadata: dict):
140
+ if metadata.get("gated", False) and not (self.args.hf_username and self.args.hf_token):
141
+ color_print("This repository requires authentication. Please provide --hf_username and --hf_token.", "RED")
142
+ sys.exit(1)
143
+
144
+ def should_regenerate_filelist(self) -> bool:
145
+ current_command = self.get_current_command()
146
+
147
+ try:
148
+ with open(self.command_file) as f:
149
+ previous_command = f.read()
150
+ if current_command == previous_command and self.fileslist_file.exists():
151
+ color_print("Using cached file list.", "GREEN")
152
+ return False
153
+ except FileNotFoundError:
154
+ pass
155
+
156
+ # 保存当前命令
157
+ with open(self.command_file, "w") as f:
158
+ f.write(current_command)
159
+ return True
160
+
161
+ def get_current_command(self) -> str:
162
+ return " ".join(sys.argv[1:])
163
+
164
+ def generate_filelist(self, metadata: dict):
165
+ color_print("Generating download file list...", "YELLOW")
166
+
167
+ include_regex = self.patterns_to_regex(self.args.include)
168
+ exclude_regex = self.patterns_to_regex(self.args.exclude)
169
+
170
+ files = []
171
+ for item in metadata.get("siblings", []):
172
+ rfilename = item.get("rfilename")
173
+ if not rfilename:
174
+ continue
175
+
176
+ # 过滤文件
177
+ if include_regex and not re.search(include_regex, rfilename):
178
+ continue
179
+ if exclude_regex and re.search(exclude_regex, rfilename):
180
+ continue
181
+
182
+ files.append(rfilename)
183
+
184
+ # 生成下载列表
185
+ with open(self.fileslist_file, "w") as f:
186
+ for file in files:
187
+ url = f"{self.hf_endpoint}/{self.args.repo_id}/resolve/{self.args.revision}/{file}"
188
+ if self.args.tool == "aria2c":
189
+ lines = [
190
+ url,
191
+ f" dir={os.path.dirname(file)}",
192
+ f" out={os.path.basename(file)}"
193
+ ]
194
+ if self.args.hf_token:
195
+ lines.append(f" header=Authorization: Bearer {self.args.hf_token}")
196
+ f.write("\n".join(lines) + "\n\n")
197
+ else:
198
+ f.write(url + "\n")
199
+
200
+ @staticmethod
201
+ def patterns_to_regex(patterns: List[str]) -> str:
202
+ if not patterns:
203
+ return ""
204
+ regexes = [translate(p).replace(r"\Z(?ms)", r"$") for p in patterns]
205
+ return "|".join(regexes)
206
+
207
+ def execute_download(self):
208
+ color_print(f"Starting download with {self.args.tool}...", "YELLOW")
209
+
210
+ os.chdir(self.local_dir)
211
+ if self.args.tool == "aria2c":
212
+ cmd = [
213
+ "aria2c",
214
+ "--console-log-level=error",
215
+ "--file-allocation=none",
216
+ "-x", str(self.args.x),
217
+ "-j", str(self.args.j),
218
+ "-s", str(self.args.x),
219
+ "-k", "1M",
220
+ "-c",
221
+ "-i", str(self.fileslist_file),
222
+ "--save-session", str(self.fileslist_file)
223
+ ]
224
+ else:
225
+ cmd = [
226
+ "wget",
227
+ "-x",
228
+ "-nH",
229
+ f"--cut-dirs={5 if self.args.dataset else 4}",
230
+ "--continue",
231
+ "--input-file", str(self.fileslist_file)
232
+ ]
233
+ if self.args.hf_token:
234
+ cmd.append(f"--header=Authorization: Bearer {self.args.hf_token}")
235
+
236
+ try:
237
+ subprocess.run(cmd, check=True)
238
+ except subprocess.CalledProcessError as e:
239
+ color_print(f"Download failed: {e}", "RED")
240
+ sys.exit(1)
241
+
242
+ class HFDNormalDownloader(HFDDownloader):
243
+ def __init__(self,
244
+ repo_id: str,
245
+ include = [],
246
+ exclude = [],
247
+ hf_username = "",
248
+ hf_token = "",
249
+ tool = "aria2c",
250
+ x = 4,
251
+ j = 5,
252
+ dataset = False,
253
+ local_dir = "",
254
+ revision = "main"):
255
+ self.args = HFDArgs()
256
+ self.args.include = include
257
+ self.args.exclude = exclude
258
+ self.args.hf_username = hf_username
259
+ self.args.hf_token = hf_token
260
+ self.args.tool = tool
261
+ self.args.x = x
262
+ self.args.j = j
263
+ self.args.dataset = dataset
264
+ self.args.repo_id = repo_id
265
+ self.args.revision = revision
266
+ self.args.local_dir = local_dir
267
+ print("Model dir:", self.args.local_dir)
268
+ self.validate_arguments()
269
+ self.setup_paths()
270
+ self.hf_endpoint = os.environ.get("HF_ENDPOINT", "https://hf-mirror.com")
271
+ # 确保缓存目录存在
272
+ (self.local_dir / ".hfd").mkdir(parents=True, exist_ok=True)
273
+
274
+ if __name__ == "__main__":
275
+ try:
276
+ downloader = HFDDownloader()
277
+ #model_name = "Qwen/Qwen2-1.5B-Instruct"
278
+ #downloader = HFDNormalDownloader(model_name, local_dir = get_fastllm_cache_path(model_name))
279
+ downloader.run()
280
+ except KeyboardInterrupt:
281
+ color_print("\nDownload interrupted.", "YELLOW")
282
+ sys.exit(1)
ftllm/export.py ADDED
@@ -0,0 +1,15 @@
1
+ import argparse
2
+ from ftllm import llm
3
+ from .util import make_normal_parser
4
+ from .util import make_normal_llm_model
5
+ import readline
6
+
7
+ def args_parser():
8
+ parser = make_normal_parser('fastllm_export')
9
+ parser.add_argument('-o', '--output', type = str, required = True, help = '导出路径')
10
+ args = parser.parse_args()
11
+ return args
12
+
13
+ if __name__ == "__main__":
14
+ args = args_parser()
15
+ llm.export_llm_model_fromhf(path = args.path, dtype = args.dtype, lora = args.lora, output = args.output)
Binary file