lxcxjxhx commited on
Commit
31514dd
·
verified ·
1 Parent(s): 3fdd0bf

Upload hos_optimizer/cli.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. hos_optimizer/cli.py +488 -0
hos_optimizer/cli.py ADDED
@@ -0,0 +1,488 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ HOS Model Optimizer - 统一命令行接口
5
+
6
+ 提供统一的命令行入口,整合所有功能模块:
7
+ - hos-quantize: 模型量化
8
+ - hos-infer: 推理服务
9
+ - hos-train: 微调训练
10
+ - hos-merge: 模型合并
11
+ - hos-deploy: 部署服务
12
+ - hos-config: 配置管理
13
+ - hos-evaluate: 模型评测
14
+ """
15
+
16
+ import click
17
+ import sys
18
+ import os
19
+
20
+ # 添加项目根目录到路径
21
+ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
22
+
23
+
24
+ @click.group()
25
+ @click.version_option(version="1.0.0", prog_name="HOS Model Optimizer")
26
+ def cli():
27
+ """HOS Model Optimizer - 小模型优化工具集
28
+
29
+ 提供模型量化、推理、训练、合并和部署的完整工具链。
30
+ 针对 8GB VRAM 场景进行了优化。
31
+
32
+ 使用示例:
33
+ hos-quantize --method gguf --model ./model --output ./model.gguf
34
+ hos-infer --model ./model --prompt "你好"
35
+ hos-train --model ./model --dataset ./data.json
36
+ hos-deploy --model ./model --port 8000
37
+ """
38
+ pass
39
+
40
+
41
+ # ============================================================
42
+ # hos-quantize: 模型量化
43
+ # ============================================================
44
+
45
+ @cli.command("quantize")
46
+ @click.option("--method", type=click.Choice(["gguf", "awq", "gptq", "evaluate", "convert"]),
47
+ required=True, help="量化方法")
48
+ @click.option("--model", type=str, required=True, help="输入模型路径")
49
+ @click.option("--output", type=str, default=None, help="输出路径")
50
+ @click.option("--bits", type=int, default=4, help="量化位数(默认: 4)")
51
+ @click.option("--quant-type", type=str, default="Q4_K_M", help="GGUF 量化类型")
52
+ @click.option("--group-size", type=int, default=128, help="量化分组大小")
53
+ @click.option("--llama-cpp-path", type=str, default=None, help="llama.cpp 安装路径")
54
+ @click.option("--from", "from_format", type=click.Choice(["gguf", "awq", "gptq", "hf"]),
55
+ help="源格式(用于格式转换)")
56
+ @click.option("--to", "to_format", type=click.Choice(["gguf", "awq", "gptq", "hf"]),
57
+ help="目标格式(用于格式转换)")
58
+ def quantize_cmd(method, model, output, bits, quant_type, group_size, llama_cpp_path,
59
+ from_format, to_format):
60
+ """量化模型
61
+
62
+ 支持多种量化方法:GGUF、AWQ、GPTQ,以及格式转换和质量评估。
63
+
64
+ 示例:
65
+ # GGUF 量化
66
+ hos-quantize --method gguf --model ./model --output ./model.gguf
67
+
68
+ # AWQ 4-bit 量化
69
+ hos-quantize --method awq --bits 4 --model ./model
70
+
71
+ # 评估 PPL
72
+ hos-quantize --method evaluate --model ./quantized-model
73
+ """
74
+ from hos_optimizer.quantize import quantize_gguf, quantize_awq, quantize_gptq, evaluate_perplexity, convert_format
75
+
76
+ if method == "gguf":
77
+ quantize_gguf(model, output or f"{model}.gguf", quant_type, llama_cpp_path)
78
+ elif method == "awq":
79
+ quantize_awq(model, output or f"{model}-awq", bits, group_size)
80
+ elif method == "gptq":
81
+ quantize_gptq(model, output or f"{model}-gptq", bits, group_size)
82
+ elif method == "evaluate":
83
+ evaluate_perplexity(model)
84
+ elif method == "convert":
85
+ if not from_format or not to_format:
86
+ click.echo("错误: 格式转换需要指定 --from 和 --to 参数", err=True)
87
+ sys.exit(1)
88
+ convert_format(model, output or f"{model}-converted", from_format, to_format)
89
+
90
+
91
+ # ============================================================
92
+ # hos-infer: 推理服务
93
+ # ============================================================
94
+
95
+ @cli.command("infer")
96
+ @click.option("--model", type=str, required=True, help="模型路径")
97
+ @click.option("--backend", type=click.Choice(["llama-cpp", "vllm", "sglang"]),
98
+ default=None, help="推理后端(默认自动检测)")
99
+ @click.option("--prompt", type=str, default=None, help="单次推理的输入提示")
100
+ @click.option("--serve", is_flag=True, help="启动 API 服务")
101
+ @click.option("--chat", is_flag=True, help="交互模式")
102
+ @click.option("--benchmark", is_flag=True, help="性能基准测试")
103
+ @click.option("--host", type=str, default="0.0.0.0", help="服务监听地址")
104
+ @click.option("--port", type=int, default=None, help="服务端口")
105
+ @click.option("--max-tokens", type=int, default=256, help="最大生成 token 数")
106
+ @click.option("--temperature", type=float, default=0.7, help="采样温度")
107
+ @click.option("--top-p", type=float, default=0.9, help="nucleus sampling 参数")
108
+ def infer_cmd(model, backend, prompt, serve, chat, benchmark, host, port,
109
+ max_tokens, temperature, top_p):
110
+ """推理服务
111
+
112
+ 支持三种推理后端:llama-cpp、vLLM、SGLang,自动选择最优后端。
113
+
114
+ 示例:
115
+ # 单次推理
116
+ hos-infer --model ./model --prompt "你好"
117
+
118
+ # 启动 API 服务
119
+ hos-infer --model ./model --serve --port 8000
120
+
121
+ # 交互模式
122
+ hos-infer --model ./model --chat
123
+ """
124
+ from hos_optimizer.inference import UnifiedInferenceEngine, run_chat, run_benchmark, run_single_inference
125
+
126
+ # 确定默认端口
127
+ default_ports = {"llama-cpp": 8080, "llama_cpp": 8080, "vllm": 8000, "sglang": 30000}
128
+ backend_key = (backend or "auto").lower().replace("-", "_")
129
+ actual_port = port or default_ports.get(backend_key, 8000)
130
+
131
+ try:
132
+ if serve:
133
+ # 启动服务模式
134
+ from hos_optimizer.inference import UnifiedInferenceEngine
135
+ engine = UnifiedInferenceEngine(model_path=model, backend=backend, auto_load=True)
136
+ engine._backend.serve(host=host, port=actual_port)
137
+ else:
138
+ # 创建推理引擎
139
+ engine = UnifiedInferenceEngine(
140
+ model_path=model,
141
+ backend=backend,
142
+ auto_load=True,
143
+ )
144
+
145
+ if chat:
146
+ run_chat(engine)
147
+ elif benchmark:
148
+ run_benchmark(engine)
149
+ elif prompt:
150
+ run_single_inference(
151
+ engine,
152
+ prompt,
153
+ max_tokens=max_tokens,
154
+ temperature=temperature,
155
+ top_p=top_p,
156
+ )
157
+ else:
158
+ click.echo("请指定 --serve, --chat, --benchmark 或 --prompt 参数")
159
+ sys.exit(1)
160
+ except Exception as e:
161
+ click.echo(f"推理错误: {e}", err=True)
162
+ sys.exit(1)
163
+
164
+
165
+ # ============================================================
166
+ # hos-train: 微调训练
167
+ # ============================================================
168
+
169
+ @cli.command("train")
170
+ @click.option("--model", type=str, required=True, help="基础模型路径")
171
+ @click.option("--dataset", type=str, required=True, help="数据集路径(JSON 文件)")
172
+ @click.option("--output", type=str, default="./output", help="输出目录")
173
+ @click.option("--method", type=click.Choice(["qlora", "lora"]), default="qlora",
174
+ help="训练方法")
175
+ @click.option("--format", "dataset_format", type=click.Choice(["alpaca", "sharegpt", "messages"]),
176
+ default="alpaca", help="数据格式")
177
+ @click.option("--max-seq-length", type=int, default=2048, help="最大序列长度")
178
+ @click.option("--lora-rank", type=int, default=16, help="LoRA rank")
179
+ @click.option("--lora-alpha", type=int, default=32, help="LoRA alpha")
180
+ @click.option("--epochs", type=int, default=3, help="训练轮数")
181
+ @click.option("--batch-size", type=int, default=2, help="批次大小")
182
+ @click.option("--grad-accum", type=int, default=8, help="梯度累积步数")
183
+ @click.option("--lr", type=float, default=2e-4, help="学习率")
184
+ @click.option("--no-unsloth", is_flag=True, help="禁用 Unsloth 加速")
185
+ @click.option("--merge", is_flag=True, help="训练后自动合并模型")
186
+ def train_cmd(model, dataset, output, method, dataset_format, max_seq_length,
187
+ lora_rank, lora_alpha, epochs, batch_size, grad_accum, lr, no_unsloth, merge):
188
+ """微调训练
189
+
190
+ 支持 QLoRA 和 LoRA 微调,针对 8GB VRAM 场景优化。
191
+
192
+ 示例:
193
+ # QLoRA 训练
194
+ hos-train --model ./model --dataset ./data.json --method qlora
195
+
196
+ # LoRA 训练并自动合并
197
+ hos-train --model ./model --dataset ./data.json --method lora --merge
198
+ """
199
+ from hos_optimizer.train import TrainingConfig, train, merge_model
200
+
201
+ # 创建训练配置
202
+ config = TrainingConfig(
203
+ model_name_or_path=model,
204
+ dataset_path=dataset,
205
+ dataset_format=dataset_format,
206
+ max_seq_length=max_seq_length,
207
+ finetuning_type=method,
208
+ use_4bit=(method == "qlora"),
209
+ lora_rank=lora_rank,
210
+ lora_alpha=lora_alpha,
211
+ output_dir=output,
212
+ num_train_epochs=epochs,
213
+ per_device_train_batch_size=batch_size,
214
+ gradient_accumulation_steps=grad_accum,
215
+ learning_rate=lr,
216
+ use_unsloth=not no_unsloth,
217
+ )
218
+
219
+ try:
220
+ # 执行训练
221
+ train(config)
222
+
223
+ # 可选:自动合并
224
+ if merge:
225
+ click.echo("开始合并模型...")
226
+ merge_model(
227
+ base_model_path=model,
228
+ adapter_path=output,
229
+ output_path=f"{output}_merged",
230
+ )
231
+ click.echo("模型合并完成!")
232
+ except Exception as e:
233
+ click.echo(f"训练错误: {e}", err=True)
234
+ sys.exit(1)
235
+
236
+
237
+ # ============================================================
238
+ # hos-merge: 合并模型
239
+ # ============================================================
240
+
241
+ @cli.command("merge")
242
+ @click.option("--base-model", type=str, required=True, help="基础模型路径")
243
+ @click.option("--adapter", type=str, required=True, help="LoRA adapter 路径")
244
+ @click.option("--output", type=str, required=True, help="输出路径")
245
+ def merge_cmd(base_model, adapter, output):
246
+ """合并模型
247
+
248
+ 将 LoRA adapter 合并到基础模型中。
249
+
250
+ 示例:
251
+ hos-merge --base-model ./base --adapter ./adapter --output ./merged
252
+ """
253
+ from hos_optimizer.train import merge_model
254
+
255
+ click.echo(f"合并模型: {base_model} + {adapter} -> {output}")
256
+ merge_model(
257
+ base_model_path=base_model,
258
+ adapter_path=adapter,
259
+ output_path=output,
260
+ )
261
+ click.echo("模型合并完成!")
262
+
263
+
264
+ # ============================================================
265
+ # hos-deploy: 部署服务
266
+ # ============================================================
267
+
268
+ @cli.command("deploy")
269
+ @click.option("--model", type=str, required=True, help="模型文件路径")
270
+ @click.option("--model-size", type=float, default=7.0, help="模型大小(十亿参数)")
271
+ @click.option("--use-case", type=click.Choice(["general", "high_concurrency", "multi_turn"]),
272
+ default="general", help="使用场景")
273
+ @click.option("--host", type=str, default="0.0.0.0", help="服务主机地址")
274
+ @click.option("--port", type=int, default=8000, help="服务端口")
275
+ @click.option("--no-auto-start", is_flag=True, help="不自动启动服务")
276
+ def deploy_cmd(model, model_size, use_case, host, port, no_auto_start):
277
+ """部署服务
278
+
279
+ 自动检测硬件并选择最优配置,一键部署 API 服务。
280
+
281
+ 示例:
282
+ # 部署 7B 模型
283
+ hos-deploy --model ./model.gguf --model-size 7.0
284
+
285
+ # 高并发场景
286
+ hos-deploy --model ./model --use-case high_concurrency
287
+ """
288
+ from hos_optimizer.deploy import HardwareDetector, ConfigSelector, ServiceLauncher
289
+
290
+ try:
291
+ # 检测硬件
292
+ hw_info = HardwareDetector.get_hardware_info()
293
+ click.echo(f"检测到硬件: {hw_info.gpu_name}, {hw_info.gpu_memory_gb:.1f}GB VRAM")
294
+
295
+ # 选择配置
296
+ config = ConfigSelector.select_config(hw_info, model_size, use_case)
297
+ click.echo(f"选择配置: {config.backend.value}, {config.quantization.value}")
298
+ click.echo(f"推荐场景: {config.recommended_for}")
299
+
300
+ # 启动服务
301
+ launcher = ServiceLauncher(config, model, host, port)
302
+ if not no_auto_start:
303
+ if launcher.start():
304
+ click.echo(f"✓ 服务已启动,监听: {host}:{port}")
305
+ else:
306
+ click.echo("错误: 服务启动失败", err=True)
307
+ sys.exit(1)
308
+ else:
309
+ click.echo("服务未自动启动(--no-auto-start)")
310
+ except Exception as e:
311
+ click.echo(f"部署错误: {e}", err=True)
312
+ sys.exit(1)
313
+
314
+
315
+ # ============================================================
316
+ # hos-config: 配置管理
317
+ # ============================================================
318
+
319
+ @cli.command("config")
320
+ @click.option("--generate", is_flag=True, help="生成最优配置")
321
+ @click.option("--scenario", type=str, help="场景名称")
322
+ @click.option("--model-path", type=str, default="", help="模型路径")
323
+ @click.option("--vram", type=float, default=8.0, help="可用显存(GB)")
324
+ @click.option("--validate", is_flag=True, help="验证配置文件")
325
+ @click.option("--config", type=str, help="配置文件路径")
326
+ @click.option("--list-templates", is_flag=True, help="列出所有模板")
327
+ @click.option("--export-template", type=str, help="导出模板")
328
+ @click.option("--output", "-o", type=str, help="输出文件路径")
329
+ def config_cmd(generate, scenario, model_path, vram, validate, config,
330
+ list_templates, export_template, output):
331
+ """配置管理
332
+
333
+ 生成 8GB VRAM 最优配置,验证配置文件,管理配置模板。
334
+
335
+ 示例:
336
+ # 生成推理配置
337
+ hos-config --generate --scenario inference_7b --model-path ./model
338
+
339
+ # 验证配置
340
+ hos-config --validate --config my_config.yaml
341
+
342
+ # 列出模板
343
+ hos-config --list-templates
344
+ """
345
+ from hos_optimizer.config import ConfigManager
346
+
347
+ manager = ConfigManager()
348
+
349
+ try:
350
+ if list_templates:
351
+ templates = manager.list_templates()
352
+ click.echo("可用的配置模板:")
353
+ for name in templates:
354
+ click.echo(f" - {name}")
355
+ elif export_template:
356
+ if not output:
357
+ click.echo("错误: 导出模板需要指定 --output 参数", err=True)
358
+ sys.exit(1)
359
+ manager.export_template(export_template, output)
360
+ click.echo(f"模板 '{export_template}' 已导出到: {output}")
361
+ elif validate:
362
+ if not config:
363
+ click.echo("错误: 验证配置需要指定 --config 参数", err=True)
364
+ sys.exit(1)
365
+ cfg = manager.load_config(config)
366
+ issues = manager.validate_config(cfg)
367
+ if not issues:
368
+ click.echo("配置验证通过 ✓")
369
+ else:
370
+ click.echo(f"发现 {len(issues)} 个问题:")
371
+ for issue in issues:
372
+ click.echo(f" {issue}")
373
+ sys.exit(1)
374
+ elif generate:
375
+ if not scenario:
376
+ click.echo("错误: 生成配置需要指定 --scenario 参数", err=True)
377
+ sys.exit(1)
378
+ cfg = manager.generate_optimal_config(
379
+ scenario=scenario,
380
+ model_path=model_path,
381
+ vram_gb=vram,
382
+ )
383
+ if output:
384
+ manager.save_config(cfg, output)
385
+ click.echo(f"配置已保存到: {output}")
386
+ else:
387
+ import yaml
388
+ click.echo(yaml.dump(cfg, default_flow_style=False, allow_unicode=True))
389
+ else:
390
+ click.echo("请指定 --generate, --validate, --list-templates 或 --export-template")
391
+ sys.exit(1)
392
+ except Exception as e:
393
+ click.echo(f"配置错误: {e}", err=True)
394
+ sys.exit(1)
395
+
396
+
397
+ # ============================================================
398
+ # hos-evaluate: 模型评测
399
+ # ============================================================
400
+
401
+ @cli.command("evaluate")
402
+ @click.option("--model", type=str, required=True, multiple=True, help="模型路径(支持多个模型对比评测)")
403
+ @click.option("--dataset", type=str, required=True, help="评测数据集路径(JSON/JSONL 格式)")
404
+ @click.option("--metrics", type=str, multiple=True, default=["bleu", "rouge"],
405
+ help="评测指标(默认: bleu rouge),支持: ppl bleu rouge exact_match f1")
406
+ @click.option("--task", type=click.Choice(["text_generation", "perplexity"]),
407
+ default="text_generation", help="任务类型(默认: text_generation)")
408
+ @click.option("--output", "-o", type=str, default=None, help="结果输出路径(支持 .json 和 .md 格式)")
409
+ @click.option("--format", "dataset_format", type=click.Choice(["alpaca", "sharegpt", "messages"]),
410
+ default=None, help="数据集格式(默认自动检测)")
411
+ @click.option("--max-samples", type=int, default=None, help="最大评测样本数(默认全部)")
412
+ @click.option("--max-seq-length", type=int, default=512, help="最大序列长度(默认 512)")
413
+ @click.option("--max-new-tokens", type=int, default=256, help="最大生成 token 数(默认 256)")
414
+ @click.option("--temperature", type=float, default=0.7, help="采样温度(默认 0.7)")
415
+ @click.option("--top-p", type=float, default=0.9, help="nucleus sampling 参数(默认 0.9)")
416
+ @click.option("--batch-size", type=int, default=1, help="推理批大小(8GB VRAM 建议 1,默认 1)")
417
+ @click.option("--output-format", type=click.Choice(["json", "markdown"]),
418
+ default="json", help="输出格式(默认: json)")
419
+ @click.option("--load-in-4bit", is_flag=True, default=False,
420
+ help="使用 4-bit 量化加载模型(节省显存,适合 8GB VRAM 场景)")
421
+ @click.option("--verbose", is_flag=True, default=False, help="启用详细日志输出")
422
+ def evaluate_cmd(model, dataset, metrics, task, output, dataset_format, max_samples,
423
+ max_seq_length, max_new_tokens, temperature, top_p, batch_size,
424
+ output_format, load_in_4bit, verbose):
425
+ """模型评测
426
+
427
+ 支持多种评测指标:PPL、BLEU、ROUGE、Exact Match、F1 等。
428
+ 支持多模型对比评测,结果可导出为 JSON 或 Markdown 格式。
429
+
430
+ 示例:
431
+ # 单模型评测
432
+ hos-evaluate --model ./model --dataset ./test.json --metrics bleu rouge
433
+
434
+ # 多模型对比
435
+ hos-evaluate --model ./model_a --model ./model_b --dataset ./test.json --metrics bleu f1
436
+
437
+ # 指定输出格式
438
+ hos-evaluate --model ./model --dataset ./test.json --output result.md
439
+
440
+ # 使用 4-bit 量化加载(8GB VRAM 优化)
441
+ hos-evaluate --model ./model --dataset ./test.json --load-in-4bit
442
+ """
443
+ from hos_optimizer.evaluate import evaluate_model, compare_models, EvaluationConfig
444
+
445
+ # 自动推断输出格式
446
+ actual_output_format = output_format
447
+ if output:
448
+ if output.endswith(".md"):
449
+ actual_output_format = "markdown"
450
+ elif output.endswith(".json"):
451
+ actual_output_format = "json"
452
+
453
+ # 构建评测配置
454
+ config = EvaluationConfig(
455
+ model_path=model[0] if len(model) == 1 else "",
456
+ dataset_path=dataset,
457
+ dataset_format=dataset_format,
458
+ max_samples=max_samples,
459
+ max_seq_length=max_seq_length,
460
+ metrics=list(metrics),
461
+ task_type=task,
462
+ max_new_tokens=max_new_tokens,
463
+ temperature=temperature,
464
+ top_p=top_p,
465
+ batch_size=batch_size,
466
+ output_format=actual_output_format,
467
+ output_path=output,
468
+ load_in_4bit=load_in_4bit,
469
+ verbose=verbose,
470
+ )
471
+
472
+ try:
473
+ if len(model) > 1:
474
+ compare_models(list(model), config)
475
+ else:
476
+ evaluate_model(config)
477
+ except Exception as e:
478
+ click.echo(f"评测错误: {e}", err=True)
479
+ sys.exit(1)
480
+
481
+
482
+ def main():
483
+ """主入口函数"""
484
+ cli()
485
+
486
+
487
+ if __name__ == "__main__":
488
+ main()