lxcxjxhx
/

lxcxjxhx commited on
Commit
10b6631
·
1 Parent(s): 1c08cb6

Add inference optimization and training scripts for Qwen3.5-0.8B-HOS

Browse files
configs/train.yaml ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ### Qwen3.5-0.8B-HOS QLoRA 训练配置 (针对 0.8B 小模型优化)
2
+
3
+ ### 模型配置
4
+ model_name_or_path: lxcxjxhx/Qwen3.5-0.8B-HOS
5
+ trust_remote_code: true
6
+
7
+ ### 训练方法
8
+ stage: sft
9
+ do_train: true
10
+ finetuning_type: lora
11
+
12
+ ### 数据集配置
13
+ dataset: merged_cybersec
14
+ dataset_dir: datasets
15
+ template: qwen
16
+ cutoff_len: 2048 # 0.8B 模型可以使用更长的序列
17
+ overwrite_cache: true
18
+ preprocessing_num_workers: 8
19
+
20
+ ### QLoRA 量化配置 (4-bit NF4)
21
+ quantization_bit: 4
22
+ quantization_method: bitsandbytes
23
+ bnb_4bit_quant_type: nf4
24
+ bnb_4bit_use_double_quant: true
25
+ bnb_4bit_compute_dtype: bfloat16
26
+
27
+ ### LoRA 配置 (0.8B 模型优化)
28
+ lora_rank: 16 # 针对小模型降低 rank
29
+ lora_alpha: 32 # alpha = 2 * rank
30
+ lora_dropout: 0.05
31
+ lora_target: all
32
+
33
+ ### 训练参数 (0.8B 模型优化,显存需求更低)
34
+ per_device_train_batch_size: 4 # 0.8B 模型可以使用更大的 batch_size
35
+ gradient_accumulation_steps: 4 # 累积 4 步,等效 batch_size = 16
36
+ learning_rate: 2e-5 # 指定的学习率
37
+ num_train_epochs: 3
38
+ lr_scheduler_type: cosine
39
+ warmup_ratio: 0.1
40
+ max_grad_norm: 1.0
41
+ logging_steps: 10
42
+ save_steps: 200
43
+ save_total_limit: 3
44
+ overwrite_output_dir: true
45
+
46
+ ### 输出配置
47
+ output_dir: outputs/qwen35-0.8b-cybersec-qlora
48
+
49
+ ### 精度配置
50
+ bf16: true
51
+
52
+ ### 性能优化
53
+ gradient_checkpointing: true # 启用梯度检查点,节省显存
54
+ optim: adamw_torch_fused # 使用融合优化器
55
+
56
+ ### HuggingFace Hub 配置
57
+ push_to_hub: true
58
+ hub_model_id: lxcxjxhx/Qwen3.5-0.8B-HOS
59
+ hub_strategy: every_save
datasets/dataset_info.json ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cybersecurity_hq": {
3
+ "file_name": "cybersecurity_hq.jsonl",
4
+ "formatting": "alpaca",
5
+ "columns": {
6
+ "prompt": "instruction",
7
+ "query": "input",
8
+ "response": "output"
9
+ }
10
+ },
11
+ "cybersecurity_sharegpt": {
12
+ "file_name": "cybersecurity_sharegpt.jsonl",
13
+ "formatting": "alpaca",
14
+ "columns": {
15
+ "prompt": "instruction",
16
+ "query": "input",
17
+ "response": "output"
18
+ }
19
+ },
20
+ "cnvd_vulnerability": {
21
+ "file_name": "cnvd_vulnerability.jsonl",
22
+ "formatting": "alpaca",
23
+ "columns": {
24
+ "prompt": "instruction",
25
+ "query": "input",
26
+ "response": "output"
27
+ }
28
+ },
29
+ "merged_cybersec": {
30
+ "file_name": "merged_cybersec.jsonl",
31
+ "formatting": "alpaca",
32
+ "columns": {
33
+ "prompt": "instruction",
34
+ "query": "input",
35
+ "response": "output"
36
+ }
37
+ }
38
+ }
inference/benchmark.py ADDED
@@ -0,0 +1,396 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Qwen3.5-0.8B-HOS 推理性能基准测试脚本
4
+
5
+ 测试指标:
6
+ - 吞吐量 (tokens/sec)
7
+ - 首token延迟 (Time to First Token, TTFT)
8
+ - 每token生成时间 (Time Per Output Token, TPOT)
9
+ - 显存占用
10
+ - 不同batch size和sequence length下的性能
11
+
12
+ 使用方法:
13
+ python benchmark.py
14
+
15
+ 可选参数:
16
+ --model: 模型路径或HF仓库ID
17
+ --backend: 推理后端 (transformers, vllm, sglang)
18
+ --batch-sizes: 测试的batch size列表,如 "1,2,4,8"
19
+ --seq-lengths: 测试的序列长度列表,如 "128,256,512,1024"
20
+ --output-len: 生成的token数量
21
+ --output: 结果保存路径
22
+ """
23
+
24
+ import argparse
25
+ import json
26
+ import time
27
+ import os
28
+ from typing import List, Dict, Any
29
+ import torch
30
+
31
+
32
+ def get_gpu_memory():
33
+ """获取GPU显存使用情况"""
34
+ if torch.cuda.is_available():
35
+ allocated = torch.cuda.memory_allocated() / 1024**3
36
+ reserved = torch.cuda.memory_reserved() / 1024**3
37
+ return {"allocated_gb": allocated, "reserved_gb": reserved}
38
+ return {"allocated_gb": 0, "reserved_gb": 0}
39
+
40
+
41
+ def benchmark_transformers(model_path: str, batch_sizes: List[int],
42
+ seq_lengths: List[int], output_len: int = 128) -> List[Dict[str, Any]]:
43
+ """使用HuggingFace Transformers进行基准测试"""
44
+ from transformers import AutoModelForCausalLM, AutoTokenizer
45
+
46
+ print(f"\n{'='*60}")
47
+ print(f"使用 Transformers 后端测试模型: {model_path}")
48
+ print(f"{'='*60}")
49
+
50
+ # 加载模型和分词器
51
+ print("加载模型...")
52
+ tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True)
53
+ model = AutoModelForCausalLM.from_pretrained(
54
+ model_path,
55
+ torch_dtype=torch.float16,
56
+ device_map="auto",
57
+ trust_remote_code=True
58
+ )
59
+ model.eval()
60
+
61
+ results = []
62
+
63
+ for batch_size in batch_sizes:
64
+ for seq_length in seq_lengths:
65
+ print(f"\n测试 batch_size={batch_size}, seq_length={seq_length}...")
66
+
67
+ # 准备输入
68
+ input_text = "信息安全是保护计算机系统" * (seq_length // 10)
69
+ inputs = tokenizer(input_text, return_tensors="pt", max_length=seq_length, truncation=True)
70
+ input_ids = inputs["input_ids"].to(model.device)
71
+
72
+ # 扩展为batch
73
+ input_ids = input_ids.repeat(batch_size, 1)
74
+
75
+ # 清空GPU缓存
76
+ torch.cuda.empty_cache()
77
+ torch.cuda.reset_peak_memory_stats()
78
+
79
+ # 预热
80
+ with torch.no_grad():
81
+ _ = model.generate(input_ids, max_new_tokens=10, do_sample=False)
82
+
83
+ # 测试首token延迟
84
+ torch.cuda.synchronize()
85
+ start_time = time.time()
86
+
87
+ with torch.no_grad():
88
+ outputs = model.generate(
89
+ input_ids,
90
+ max_new_tokens=output_len,
91
+ do_sample=False,
92
+ temperature=1.0,
93
+ use_cache=True
94
+ )
95
+
96
+ torch.cuda.synchronize()
97
+ total_time = time.time() - start_time
98
+
99
+ # 计算指标
100
+ generated_tokens = outputs.shape[1] - input_ids.shape[1]
101
+ total_tokens = generated_tokens * batch_size
102
+ throughput = total_tokens / total_time
103
+
104
+ # 估算TTFT(使用第一次生成的时间)
105
+ ttft = total_time / (generated_tokens + 1) * 1000 # 转换为毫秒
106
+
107
+ # 计算TPOT
108
+ tpot = (total_time * 1000) / generated_tokens # 转换为毫秒
109
+
110
+ # 获取显存使用
111
+ memory = get_gpu_memory()
112
+
113
+ result = {
114
+ "backend": "transformers",
115
+ "batch_size": batch_size,
116
+ "seq_length": seq_length,
117
+ "output_length": output_len,
118
+ "generated_tokens": generated_tokens,
119
+ "total_time_sec": total_time,
120
+ "throughput_tokens_per_sec": throughput,
121
+ "ttft_ms": ttft,
122
+ "tpot_ms": tpot,
123
+ "memory_allocated_gb": memory["allocated_gb"],
124
+ "memory_reserved_gb": memory["reserved_gb"],
125
+ }
126
+
127
+ results.append(result)
128
+
129
+ print(f" 吞吐量: {throughput:.2f} tokens/sec")
130
+ print(f" TTFT: {ttft:.2f} ms")
131
+ print(f" TPOT: {tpot:.2f} ms")
132
+ print(f" 显存占用: {memory['allocated_gb']:.2f} GB")
133
+
134
+ # 释放模型
135
+ del model, tokenizer
136
+ torch.cuda.empty_cache()
137
+
138
+ return results
139
+
140
+
141
+ def benchmark_vllm(model_path: str, batch_sizes: List[int],
142
+ seq_lengths: List[int], output_len: int = 128) -> List[Dict[str, Any]]:
143
+ """使用vLLM进行基准测试"""
144
+ try:
145
+ from vllm import LLM, SamplingParams
146
+ except ImportError:
147
+ print("错误: 未安装vLLM,请运行: pip install vllm")
148
+ return []
149
+
150
+ print(f"\n{'='*60}")
151
+ print(f"使用 vLLM 后端测试模型: {model_path}")
152
+ print(f"{'='*60}")
153
+
154
+ # 初始化vLLM引擎
155
+ print("初始化vLLM引擎...")
156
+ llm = LLM(
157
+ model=model_path,
158
+ dtype="float16",
159
+ trust_remote_code=True,
160
+ max_model_len=2048,
161
+ gpu_memory_utilization=0.9
162
+ )
163
+
164
+ results = []
165
+
166
+ for batch_size in batch_sizes:
167
+ for seq_length in seq_lengths:
168
+ print(f"\n测试 batch_size={batch_size}, seq_length={seq_length}...")
169
+
170
+ # 准备输入
171
+ input_text = "信息安全是保护计算机系统" * (seq_length // 10)
172
+ prompts = [input_text] * batch_size
173
+
174
+ # 采样参数
175
+ sampling_params = SamplingParams(
176
+ temperature=1.0,
177
+ max_tokens=output_len,
178
+ use_beam_search=False
179
+ )
180
+
181
+ # 清空GPU缓存
182
+ torch.cuda.empty_cache()
183
+ torch.cuda.reset_peak_memory_stats()
184
+
185
+ # 测试
186
+ torch.cuda.synchronize()
187
+ start_time = time.time()
188
+
189
+ outputs = llm.generate(prompts, sampling_params)
190
+
191
+ torch.cuda.synchronize()
192
+ total_time = time.time() - start_time
193
+
194
+ # 计算指标
195
+ generated_tokens = sum(len(output.outputs[0].token_ids) for output in outputs)
196
+ total_tokens = generated_tokens
197
+ throughput = total_tokens / total_time
198
+
199
+ # 估算TTFT和TPOT
200
+ ttft = total_time / (generated_tokens / batch_size + 1) * 1000
201
+ tpot = (total_time * 1000) / (generated_tokens / batch_size)
202
+
203
+ # 获取显存使用
204
+ memory = get_gpu_memory()
205
+
206
+ result = {
207
+ "backend": "vllm",
208
+ "batch_size": batch_size,
209
+ "seq_length": seq_length,
210
+ "output_length": output_len,
211
+ "generated_tokens": generated_tokens,
212
+ "total_time_sec": total_time,
213
+ "throughput_tokens_per_sec": throughput,
214
+ "ttft_ms": ttft,
215
+ "tpot_ms": tpot,
216
+ "memory_allocated_gb": memory["allocated_gb"],
217
+ "memory_reserved_gb": memory["reserved_gb"],
218
+ }
219
+
220
+ results.append(result)
221
+
222
+ print(f" 吞吐量: {throughput:.2f} tokens/sec")
223
+ print(f" TTFT: {ttft:.2f} ms")
224
+ print(f" TPOT: {tpot:.2f} ms")
225
+ print(f" 显存占用: {memory['allocated_gb']:.2f} GB")
226
+
227
+ return results
228
+
229
+
230
+ def benchmark_sglang(model_path: str, batch_sizes: List[int],
231
+ seq_lengths: List[int], output_len: int = 128) -> List[Dict[str, Any]]:
232
+ """使用SGLang进行基准测试"""
233
+ try:
234
+ import sglang as sgl
235
+ except ImportError:
236
+ print("错误: 未安装SGLang,请运行: pip install sglang")
237
+ return []
238
+
239
+ print(f"\n{'='*60}")
240
+ print(f"使用 SGLang 后端测试模型: {model_path}")
241
+ print(f"{'='*60}")
242
+
243
+ # 初始化SGLang运行时
244
+ print("初始化SGLang运行时...")
245
+ runtime = sgl.Runtime(
246
+ model_path=model_path,
247
+ trust_remote_code=True,
248
+ context_length=2048,
249
+ )
250
+ sgl.set_default_backend(runtime)
251
+
252
+ results = []
253
+
254
+ for batch_size in batch_sizes:
255
+ for seq_length in seq_lengths:
256
+ print(f"\n测试 batch_size={batch_size}, seq_length={seq_length}...")
257
+
258
+ # 准备输入
259
+ input_text = "信息安全是保护计算机系统" * (seq_length // 10)
260
+ prompts = [input_text] * batch_size
261
+
262
+ # 清空GPU缓存
263
+ torch.cuda.empty_cache()
264
+ torch.cuda.reset_peak_memory_stats()
265
+
266
+ # 测试
267
+ torch.cuda.synchronize()
268
+ start_time = time.time()
269
+
270
+ @sgl.function
271
+ def generate_text(s, prompt):
272
+ s += prompt + sgl.gen("response", max_tokens=output_len, temperature=1.0)
273
+
274
+ states = [generate_text.run(prompt=p) for p in prompts]
275
+
276
+ torch.cuda.synchronize()
277
+ total_time = time.time() - start_time
278
+
279
+ # 计算指标
280
+ generated_tokens = sum(len(state["response"]) for state in states) // 2 # 粗略估算
281
+ total_tokens = generated_tokens
282
+ throughput = total_tokens / total_time
283
+
284
+ # 估算TTFT和TPOT
285
+ ttft = total_time / (generated_tokens / batch_size + 1) * 1000
286
+ tpot = (total_time * 1000) / (generated_tokens / batch_size)
287
+
288
+ # 获取显存使用
289
+ memory = get_gpu_memory()
290
+
291
+ result = {
292
+ "backend": "sglang",
293
+ "batch_size": batch_size,
294
+ "seq_length": seq_length,
295
+ "output_length": output_len,
296
+ "generated_tokens": generated_tokens,
297
+ "total_time_sec": total_time,
298
+ "throughput_tokens_per_sec": throughput,
299
+ "ttft_ms": ttft,
300
+ "tpot_ms": tpot,
301
+ "memory_allocated_gb": memory["allocated_gb"],
302
+ "memory_reserved_gb": memory["reserved_gb"],
303
+ }
304
+
305
+ results.append(result)
306
+
307
+ print(f" 吞吐量: {throughput:.2f} tokens/sec")
308
+ print(f" TTFT: {ttft:.2f} ms")
309
+ print(f" TPOT: {tpot:.2f} ms")
310
+ print(f" 显存占用: {memory['allocated_gb']:.2f} GB")
311
+
312
+ runtime.shutdown()
313
+
314
+ return results
315
+
316
+
317
+ def print_summary(results: List[Dict[str, Any]]):
318
+ """打印测试结果汇总"""
319
+ if not results:
320
+ print("\n没有测试结果可汇总")
321
+ return
322
+
323
+ print(f"\n{'='*80}")
324
+ print("基准测试结果汇总")
325
+ print(f"{'='*80}")
326
+ print(f"{'Backend':<12} {'Batch':<6} {'SeqLen':<7} {'OutLen':<7} "
327
+ f"{'Throughput':<12} {'TTFT(ms)':<10} {'TPOT(ms)':<10} {'Memory(GB)':<11}")
328
+ print(f"{'-'*80}")
329
+
330
+ for r in results:
331
+ print(f"{r['backend']:<12} {r['batch_size']:<6} {r['seq_length']:<7} "
332
+ f"{r['output_length']:<7} {r['throughput_tokens_per_sec']:<12.2f} "
333
+ f"{r['ttft_ms']:<10.2f} {r['tpot_ms']:<10.2f} {r['memory_allocated_gb']:<11.2f}")
334
+
335
+ print(f"{'='*80}\n")
336
+
337
+
338
+ def main():
339
+ """主函数"""
340
+ parser = argparse.ArgumentParser(description="Qwen3.5-0.8B-HOS 推理性能基准测试")
341
+ parser.add_argument("--model", type=str, default="lxcxjxhx/Qwen3.5-0.8B-HOS",
342
+ help="模型路径或HF仓库ID")
343
+ parser.add_argument("--backend", type=str, default="transformers",
344
+ choices=["transformers", "vllm", "sglang"],
345
+ help="推理后端")
346
+ parser.add_argument("--batch-sizes", type=str, default="1,2,4,8",
347
+ help="测试的batch size列表,如 '1,2,4,8'")
348
+ parser.add_argument("--seq-lengths", type=str, default="128,256,512,1024",
349
+ help="测试的序列长度列表,如 '128,256,512,1024'")
350
+ parser.add_argument("--output-len", type=int, default=128,
351
+ help="生成的token数量")
352
+ parser.add_argument("--output", type=str, default="benchmark_results.json",
353
+ help="结果保存路径")
354
+
355
+ args = parser.parse_args()
356
+
357
+ # 解析列表参数
358
+ batch_sizes = [int(x) for x in args.batch_sizes.split(",")]
359
+ seq_lengths = [int(x) for x in args.seq_lengths.split(",")]
360
+
361
+ print(f"\n{'='*60}")
362
+ print(f"Qwen3.5-0.8B-HOS 推理性能基准测试")
363
+ print(f"{'='*60}")
364
+ print(f"模型: {args.model}")
365
+ print(f"后端: {args.backend}")
366
+ print(f"Batch sizes: {batch_sizes}")
367
+ print(f"Sequence lengths: {seq_lengths}")
368
+ print(f"Output length: {args.output_len}")
369
+ print(f"{'='*60}\n")
370
+
371
+ # 执行基准测试
372
+ if args.backend == "transformers":
373
+ results = benchmark_transformers(
374
+ args.model, batch_sizes, seq_lengths, args.output_len
375
+ )
376
+ elif args.backend == "vllm":
377
+ results = benchmark_vllm(
378
+ args.model, batch_sizes, seq_lengths, args.output_len
379
+ )
380
+ elif args.backend == "sglang":
381
+ results = benchmark_sglang(
382
+ args.model, batch_sizes, seq_lengths, args.output_len
383
+ )
384
+
385
+ # 打印汇总
386
+ print_summary(results)
387
+
388
+ # 保存结果
389
+ if results:
390
+ with open(args.output, 'w', encoding='utf-8') as f:
391
+ json.dump(results, f, indent=2, ensure_ascii=False)
392
+ print(f"结果已保存到: {args.output}")
393
+
394
+
395
+ if __name__ == "__main__":
396
+ main()
inference/quantize.py ADDED
@@ -0,0 +1,306 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Qwen3.5-0.8B-HOS 量化脚本
4
+
5
+ 支持量化方法:
6
+ - AWQ 4-bit:激活感知量化,精度损失最小
7
+ - GPTQ 8-bit:基于 GPU 的 Post-Training 量化
8
+ - bitsandbytes 4/8-bit:动态量化,无需校准数据
9
+
10
+ 使用方法:
11
+ # AWQ 4-bit 量化
12
+ python quantize.py --method awq --bits 4
13
+
14
+ # GPTQ 8-bit 量化
15
+ python quantize.py --method gptq --bits 8
16
+
17
+ # bitsandbytes 4-bit 动态量化(最简单,无需校准数据)
18
+ python quantize.py --method bnb --bits 4
19
+
20
+ # 测试量化后模型
21
+ python quantize.py --method awq --bits 4 --test
22
+ """
23
+
24
+ import argparse
25
+ import os
26
+ import time
27
+ import torch
28
+
29
+
30
+ def quantize_awq(model_path, output_path, bits=4):
31
+ """AWQ 4-bit 量化
32
+
33
+ AWQ (Activation-aware Weight Quantization) 通过保护显著权重通道
34
+ 实现高精度 4-bit 量化,适合 0.8B 小模型。
35
+ """
36
+ print(f"=== AWQ {bits}-bit 量化 ===")
37
+ print(f"输入模型: {model_path}")
38
+ print(f"输出路径: {output_path}")
39
+
40
+ try:
41
+ from awq import AutoAWQForCausalLM
42
+ from transformers import AutoTokenizer
43
+
44
+ # 加载模型和分词器
45
+ print("加载模型...")
46
+ model = AutoAWQForCausalLM.from_pretrained(
47
+ model_path,
48
+ trust_remote_code=True,
49
+ )
50
+ tokenizer = AutoTokenizer.from_pretrained(
51
+ model_path,
52
+ trust_remote_code=True,
53
+ )
54
+
55
+ # AWQ 量化配置
56
+ quant_config = {
57
+ "zero_point": True,
58
+ "q_group_size": 128,
59
+ "w_bit": bits,
60
+ "version": "GEMM",
61
+ }
62
+
63
+ print(f"开始 AWQ {bits}-bit 量化...")
64
+ model.quantize(tokenizer, quant_config=quant_config)
65
+
66
+ # 保存量化模型
67
+ print(f"保存量化模型到: {output_path}")
68
+ model.save_quantized(output_path)
69
+ tokenizer.save_pretrained(output_path)
70
+
71
+ print("AWQ 量化完成!")
72
+ return output_path
73
+
74
+ except ImportError:
75
+ print("错误: 需要安装 autoawq")
76
+ print(" pip install autoawq")
77
+ return None
78
+
79
+
80
+ def quantize_gptq(model_path, output_path, bits=8):
81
+ """GPTQ 量化
82
+
83
+ GPTQ 基于 Optimal Brain Quantization 框架,通过逐层量化
84
+ 和误差补偿实现高精度量化。
85
+ """
86
+ print(f"=== GPTQ {bits}-bit 量化 ===")
87
+ print(f"输入模型: {model_path}")
88
+ print(f"输出路径: {output_path}")
89
+
90
+ try:
91
+ from transformers import AutoTokenizer
92
+ from auto_gptq import AutoGPTQForCausalLM, BaseQuantizeConfig
93
+
94
+ tokenizer = AutoTokenizer.from_pretrained(
95
+ model_path,
96
+ trust_remote_code=True,
97
+ )
98
+
99
+ # GPTQ 量化配置
100
+ quantize_config = BaseQuantizeConfig(
101
+ bits=bits,
102
+ group_size=128,
103
+ desc_act=False,
104
+ damp_percent=0.1,
105
+ )
106
+
107
+ print("加载模型...")
108
+ model = AutoGPTQForCausalLM.from_pretrained(
109
+ model_path,
110
+ quantize_config,
111
+ trust_remote_code=True,
112
+ )
113
+
114
+ # 准备校准数据(使用简单文本)
115
+ print("准备校准数据...")
116
+ calibration_texts = [
117
+ "信息安全是保护计算机系统和网络免受未经授权的访问、攻击或破坏的实践。",
118
+ "SQL注入是一种常见的Web安全漏洞,攻击者通过注入恶意SQL代码来操纵数据库。",
119
+ "渗透测试是一种模拟真实攻击者的方法来评估系统安全性的技术。",
120
+ "防火墙是网络安全的第一道防线,用于监控和控制进出网络的流量。",
121
+ ]
122
+
123
+ calibration_data = [
124
+ tokenizer(text, return_tensors="pt", max_length=512, truncation=True)
125
+ for text in calibration_texts
126
+ ]
127
+
128
+ print(f"开始 GPTQ {bits}-bit 量化...")
129
+ model.quantize(calibration_data)
130
+
131
+ print(f"保存量化模型到: {output_path}")
132
+ model.save_quantized(output_path)
133
+ tokenizer.save_pretrained(output_path)
134
+
135
+ print("GPTQ 量化完成!")
136
+ return output_path
137
+
138
+ except ImportError:
139
+ print("错误: 需要安装 auto-gptq")
140
+ print(" pip install auto-gptq")
141
+ return None
142
+
143
+
144
+ def quantize_bnb(model_path, output_path, bits=4):
145
+ """bitsandbytes 动态量化
146
+
147
+ 最简单的量化方法,无需校准数据,适合快速部署。
148
+ 支持 4-bit (NF4/FP4) 和 8-bit 量化。
149
+ """
150
+ print(f"=== bitsandbytes {bits}-bit 量化 ===")
151
+ print(f"输入模型: {model_path}")
152
+ print(f"输出路径: {output_path}")
153
+
154
+ from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
155
+
156
+ if bits == 4:
157
+ quant_config = BitsAndBytesConfig(
158
+ load_in_4bit=True,
159
+ bnb_4bit_quant_type="nf4",
160
+ bnb_4bit_use_double_quant=True,
161
+ bnb_4bit_compute_dtype=torch.bfloat16,
162
+ )
163
+ elif bits == 8:
164
+ quant_config = BitsAndBytesConfig(
165
+ load_in_8bit=True,
166
+ )
167
+ else:
168
+ raise ValueError(f"不支持的量化位数: {bits}")
169
+
170
+ print("加载并量化模型...")
171
+ model = AutoModelForCausalLM.from_pretrained(
172
+ model_path,
173
+ quantization_config=quant_config,
174
+ trust_remote_code=True,
175
+ device_map="auto",
176
+ )
177
+ tokenizer = AutoTokenizer.from_pretrained(
178
+ model_path,
179
+ trust_remote_code=True,
180
+ )
181
+
182
+ # 保存量化后的模型
183
+ os.makedirs(output_path, exist_ok=True)
184
+ print(f"保存量化模型到: {output_path}")
185
+ model.save_pretrained(output_path)
186
+ tokenizer.save_pretrained(output_path)
187
+
188
+ print(f"bitsandbytes {bits}-bit 量化完成!")
189
+ return output_path
190
+
191
+
192
+ def test_quantized_model(model_path, bits=4):
193
+ """测试量化后模型的推理"""
194
+ from transformers import AutoModelForCausalLM, AutoTokenizer
195
+
196
+ print(f"\n=== 测试量化模型 ===")
197
+ print(f"模型路径: {model_path}")
198
+
199
+ tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True)
200
+
201
+ # 根据量化方法选择加载方式
202
+ if "awq" in model_path.lower() or "gptq" in model_path.lower():
203
+ model = AutoModelForCausalLM.from_pretrained(
204
+ model_path,
205
+ trust_remote_code=True,
206
+ device_map="auto",
207
+ )
208
+ else:
209
+ # bitsandbytes 量化模型
210
+ quant_config = BitsAndBytesConfig(
211
+ load_in_4bit=(bits == 4),
212
+ load_in_8bit=(bits == 8),
213
+ bnb_4bit_quant_type="nf4",
214
+ bnb_4bit_use_double_quant=True,
215
+ bnb_4bit_compute_dtype=torch.bfloat16,
216
+ )
217
+ model = AutoModelForCausalLM.from_pretrained(
218
+ model_path,
219
+ quantization_config=quant_config,
220
+ trust_remote_code=True,
221
+ device_map="auto",
222
+ )
223
+
224
+ # 测试推理
225
+ test_prompts = [
226
+ "什么是DDoS攻击?",
227
+ "如何防范中间人攻击?",
228
+ ]
229
+
230
+ for prompt in test_prompts:
231
+ inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
232
+
233
+ start = time.time()
234
+ with torch.no_grad():
235
+ outputs = model.generate(
236
+ **inputs,
237
+ max_new_tokens=128,
238
+ temperature=0.7,
239
+ top_p=0.9,
240
+ )
241
+ elapsed = time.time() - start
242
+
243
+ response = tokenizer.decode(outputs[0], skip_special_tokens=True)
244
+ token_count = outputs.shape[1] - inputs["input_ids"].shape[1]
245
+
246
+ print(f"\nPrompt: {prompt}")
247
+ print(f"回复: {response[:200]}...")
248
+ print(f"生成 {token_count} tokens, 耗时 {elapsed:.2f}s")
249
+
250
+
251
+ def get_model_size(model_path):
252
+ """获取模型文件大小"""
253
+ total_size = 0
254
+ for dirpath, _, filenames in os.walk(model_path):
255
+ for f in filenames:
256
+ if f.endswith((".safetensors", ".bin", ".pt")):
257
+ fp = os.path.join(dirpath, f)
258
+ total_size += os.path.getsize(fp)
259
+ return total_size / (1024 ** 3) # GB
260
+
261
+
262
+ if __name__ == "__main__":
263
+ parser = argparse.ArgumentParser(description="Qwen3.5-0.8B-HOS 量化脚本")
264
+ parser.add_argument("--model", type=str, default="lxcxjxhx/Qwen3.5-0.8B-HOS",
265
+ help="输入模型路径")
266
+ parser.add_argument("--output", type=str, default=None,
267
+ help="输出模型路径")
268
+ parser.add_argument("--method", type=str, default="bnb",
269
+ choices=["awq", "gptq", "bnb"],
270
+ help="量化方法")
271
+ parser.add_argument("--bits", type=int, default=4, choices=[4, 8],
272
+ help="量化位数")
273
+ parser.add_argument("--test", action="store_true",
274
+ help="测试量化后模型")
275
+
276
+ args = parser.parse_args()
277
+
278
+ if args.output is None:
279
+ args.output = f"{args.model.replace('/', '-')}-{args.method}-{args.bits}bit"
280
+
281
+ print(f"量化方法: {args.method}")
282
+ print(f"量化位数: {args.bits}-bit")
283
+
284
+ # 量化前模型大小
285
+ if os.path.exists(args.model):
286
+ original_size = get_model_size(args.model)
287
+ print(f"原始模型大小: {original_size:.2f} GB")
288
+
289
+ # 执行量化
290
+ if args.method == "awq":
291
+ result = quantize_awq(args.model, args.output, args.bits)
292
+ elif args.method == "gptq":
293
+ result = quantize_gptq(args.model, args.output, args.bits)
294
+ elif args.method == "bnb":
295
+ result = quantize_bnb(args.model, args.output, args.bits)
296
+
297
+ if result and os.path.exists(result):
298
+ quantized_size = get_model_size(result)
299
+ print(f"\n量化后模型大小: {quantized_size:.2f} GB")
300
+ if os.path.exists(args.model):
301
+ compression = (1 - quantized_size / original_size) * 100
302
+ print(f"压缩率: {compression:.1f}%")
303
+
304
+ # 测试
305
+ if args.test and result:
306
+ test_quantized_model(result, args.bits)
inference/sglang_serve.py ADDED
@@ -0,0 +1,225 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Qwen3.5-0.8B-HOS SGLang 推理服务
4
+
5
+ 支持特性:
6
+ - RadixAttention:前缀缓存加速,重复前缀请求速度提升 3-5x
7
+ - JSON Schema 约束生成:确保输出符合指定格式
8
+ - 高效连续批处理
9
+
10
+ 使用方法:
11
+ # 启动服务
12
+ python sglang_serve.py --serve
13
+
14
+ # 带 JSON 约束的推理测试
15
+ python sglang_serve.py --test
16
+
17
+ # 命令行交互
18
+ python sglang_serve.py --chat
19
+ """
20
+
21
+ import argparse
22
+ import json
23
+ import time
24
+ import torch
25
+
26
+
27
+ def load_model(model_path="lxcxjxhx/Qwen3.5-0.8B-HOS"):
28
+ """加载模型到 SGLang 引擎"""
29
+ import sglang as sgl
30
+
31
+ # RadixAttention 自动启用
32
+ runtime = sgl.Runtime(
33
+ model_path=model_path,
34
+ # 0.8B 小模型配置
35
+ tp_size=1,
36
+ mem_fraction_static=0.9,
37
+ context_length=8192,
38
+ trust_remote_code=True,
39
+ )
40
+ return runtime
41
+
42
+
43
+ def serve(model_path="lxcxjxhx/Qwen3.5-0.8B-HOS", host="0.0.0.0", port=30000):
44
+ """启动 SGLang OpenAI 兼容 API 服务
45
+
46
+ 启动后可通过以下方式调用:
47
+ curl http://localhost:30000/v1/completions \\
48
+ -H "Content-Type: application/json" \\
49
+ -d '{"model": "Qwen3.5-0.8B-HOS", "prompt": "什么是SQL注入?", "max_tokens": 256}'
50
+ """
51
+ import sglang as sgl
52
+
53
+ print(f"启动 SGLang 服务: http://{host}:{port}")
54
+ print(f"模型: {model_path}")
55
+ print("特性: RadixAttention, Continuous Batching, JSON Schema 约束生成")
56
+
57
+ runtime = sgl.Runtime(
58
+ model_path=model_path,
59
+ host=host,
60
+ port=port,
61
+ tp_size=1,
62
+ mem_fraction_static=0.9,
63
+ context_length=8192,
64
+ trust_remote_code=True,
65
+ )
66
+
67
+ print(f"\n服务已启动,按 Ctrl+C 停止")
68
+ try:
69
+ runtime.loop()
70
+ except KeyboardInterrupt:
71
+ runtime.shutdown()
72
+ print("\n服务已停止")
73
+
74
+
75
+ def test_json_constrained(model_path="lxcxjxhx/Qwen3.5-0.8B-HOS"):
76
+ """测试 JSON Schema 约束生成"""
77
+ import sglang as sgl
78
+
79
+ print(f"加载模型: {model_path}")
80
+ sgl.set_default_backend(sgl.RuntimeEndpoint(f"http://localhost:30000"))
81
+
82
+ # 定义安全事件分析的 JSON Schema
83
+ security_schema = {
84
+ "type": "object",
85
+ "properties": {
86
+ "threat_level": {"type": "string", "enum": ["low", "medium", "high", "critical"]},
87
+ "attack_type": {"type": "string"},
88
+ "affected_system": {"type": "string"},
89
+ "recommendation": {"type": "string"},
90
+ "confidence": {"type": "number", "minimum": 0, "maximum": 1},
91
+ },
92
+ "required": ["threat_level", "attack_type", "recommendation"],
93
+ }
94
+
95
+ @sgl.function
96
+ def analyze_security(s, log_entry):
97
+ s += f"分析以下安全日志条目,以 JSON 格式输出分析结果:\n{log_entry}\n"
98
+ s += sgl.gen("analysis", max_tokens=256, json_schema=security_schema)
99
+
100
+ test_logs = [
101
+ "2024-01-15 03:22:11 ALERT: Multiple failed SSH login attempts from 192.168.1.100 (50 attempts in 5 minutes)",
102
+ "2024-01-15 04:15:33 WARNING: Unusual outbound traffic detected from server-web-01 to external IP 203.0.113.50 on port 4444",
103
+ "2024-01-15 05:01:22 CRITICAL: SQL injection pattern detected in web request: ' OR 1=1; DROP TABLE users;--",
104
+ ]
105
+
106
+ print(f"\n=== JSON 约束生成测试 ===")
107
+ for i, log in enumerate(test_logs):
108
+ start = time.time()
109
+ state = analyze_security.run(log=log)
110
+ elapsed = time.time() - start
111
+
112
+ print(f"\n--- 日志 {i+1} ---")
113
+ print(f"输入: {log[:80]}...")
114
+ try:
115
+ result = json.loads(state["analysis"])
116
+ print(f"分析结果: {json.dumps(result, indent=2, ensure_ascii=False)}")
117
+ except json.JSONDecodeError:
118
+ print(f"原始输出: {state['analysis']}")
119
+ print(f"耗时: {elapsed:.2f}s")
120
+
121
+
122
+ def test_inference(model_path="lxcxjxhx/Qwen3.5-0.8B-HOS"):
123
+ """基础推理测试"""
124
+ import sglang as sgl
125
+
126
+ print(f"加载模型: {model_path}")
127
+ runtime = load_model(model_path)
128
+
129
+ prompts = [
130
+ "请解释SQL注入攻击的原理和防御方法。",
131
+ "什么是XSS跨站脚本攻击?如何防范?",
132
+ "描述一次渗透测试的完整流程。",
133
+ ]
134
+
135
+ print(f"\n=== SGLang 推理测试 ===")
136
+ start = time.time()
137
+
138
+ # 使用 RadixAttention 的批处理推理
139
+ outputs = runtime.generate(
140
+ prompts,
141
+ sampling_params={
142
+ "temperature": 0.7,
143
+ "top_p": 0.9,
144
+ "max_new_tokens": 256,
145
+ },
146
+ )
147
+
148
+ elapsed = time.time() - start
149
+ total_tokens = sum(len(o["token_ids"]) for o in outputs)
150
+
151
+ print(f"总耗时: {elapsed:.2f}s")
152
+ print(f"总 token 数: {total_tokens}")
153
+ print(f"吞吐量: {total_tokens / elapsed:.1f} tokens/s")
154
+
155
+ for i, output in enumerate(outputs):
156
+ print(f"\n--- 请求 {i+1} ---")
157
+ print(f"生成: {output['text'][:200]}...")
158
+
159
+ runtime.shutdown()
160
+
161
+
162
+ def chat_mode(model_path="lxcxjxhx/Qwen3.5-0.8B-HOS"):
163
+ """命令行交互模式"""
164
+ runtime = load_model(model_path)
165
+
166
+ print("\n=== Qwen3.5-0.8B-HOS SGLang 交互模式 ===")
167
+ print("输入 'quit' 或 'exit' 退出\n")
168
+
169
+ while True:
170
+ try:
171
+ user_input = input("用户: ").strip()
172
+ if user_input.lower() in ("quit", "exit", "q"):
173
+ break
174
+ if not user_input:
175
+ continue
176
+
177
+ prompt = f"<|im_start|>user\n{user_input}<|im_end|>\n<|im_start|>assistant\n"
178
+
179
+ start = time.time()
180
+ outputs = runtime.generate(
181
+ [prompt],
182
+ sampling_params={
183
+ "temperature": 0.7,
184
+ "top_p": 0.9,
185
+ "max_new_tokens": 512,
186
+ },
187
+ )
188
+ elapsed = time.time() - start
189
+
190
+ response = outputs[0]["text"]
191
+ token_count = len(outputs[0]["token_ids"])
192
+
193
+ print(f"\n助手: {response}")
194
+ print(f"[{token_count} tokens, {elapsed:.2f}s, {token_count/elapsed:.1f} tokens/s]\n")
195
+
196
+ except KeyboardInterrupt:
197
+ break
198
+
199
+ runtime.shutdown()
200
+ print("\n再见!")
201
+
202
+
203
+ if __name__ == "__main__":
204
+ parser = argparse.ArgumentParser(description="Qwen3.5-0.8B-HOS SGLang 推理服务")
205
+ parser.add_argument("--model", type=str, default="lxcxjxhx/Qwen3.5-0.8B-HOS",
206
+ help="模型路径或 HF 仓库 ID")
207
+ parser.add_argument("--serve", action="store_true", help="启动 API 服务")
208
+ parser.add_argument("--test", action="store_true", help="运行推理测试")
209
+ parser.add_argument("--test-json", action="store_true", help="测试 JSON 约束生成")
210
+ parser.add_argument("--chat", action="store_true", help="命令行交互模式")
211
+ parser.add_argument("--host", type=str, default="0.0.0.0", help="服务监听地址")
212
+ parser.add_argument("--port", type=int, default=30000, help="服务端口")
213
+
214
+ args = parser.parse_args()
215
+
216
+ if args.serve:
217
+ serve(args.model, args.host, args.port)
218
+ elif args.test:
219
+ test_inference(args.model)
220
+ elif args.test_json:
221
+ test_json_constrained(args.model)
222
+ elif args.chat:
223
+ chat_mode(args.model)
224
+ else:
225
+ serve(args.model, args.host, args.port)
inference/vllm_serve.py ADDED
@@ -0,0 +1,184 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Qwen3.5-0.8B-HOS vLLM 推理服务
4
+
5
+ 支持特性:
6
+ - PagedAttention:动态 KV cache 管理,提升吞吐量
7
+ - Continuous Batching:动态批处理,减少 padding 开销
8
+ - torch.compile 算子融合加速
9
+
10
+ 使用方法:
11
+ # 启动 OpenAI 兼容 API 服务
12
+ python vllm_serve.py --serve
13
+
14
+ # 直接推理测试
15
+ python vllm_serve.py --test
16
+
17
+ # 命令行交互
18
+ python vllm_serve.py --chat
19
+ """
20
+
21
+ import argparse
22
+ import time
23
+ import torch
24
+
25
+
26
+ def load_model(model_path="lxcxjxhx/Qwen3.5-0.8B-HOS", dtype="bfloat16"):
27
+ """加载模型到 vLLM 引擎"""
28
+ from vllm import LLM, SamplingParams
29
+
30
+ dtype_map = {
31
+ "bfloat16": torch.bfloat16,
32
+ "float16": torch.float16,
33
+ "float32": torch.float32,
34
+ }
35
+
36
+ llm = LLM(
37
+ model=model_path,
38
+ dtype=dtype_map.get(dtype, torch.bfloat16),
39
+ # PagedAttention 配置
40
+ gpu_memory_utilization=0.9,
41
+ max_model_len=8192,
42
+ # Continuous Batching(vLLM 默认启用)
43
+ max_num_seqs=256,
44
+ # 0.8B 小模型单卡无需 tensor parallelism
45
+ tensor_parallel_size=1,
46
+ # 启用 torch.compile 加速
47
+ enforce_eager=False,
48
+ trust_remote_code=True,
49
+ )
50
+ return llm
51
+
52
+
53
+ def serve(model_path="lxcxjxhx/Qwen3.5-0.8B-HOS", host="0.0.0.0", port=8000):
54
+ """启动 OpenAI 兼容 API 服务
55
+
56
+ 启动后可通过以下方式调用:
57
+ curl http://localhost:8000/v1/completions \\
58
+ -H "Content-Type: application/json" \\
59
+ -d '{"model": "Qwen3.5-0.8B-HOS", "prompt": "什么是SQL注入?", "max_tokens": 256}'
60
+ """
61
+ print(f"启动 vLLM 服务: http://{host}:{port}")
62
+ print(f"模型: {model_path}")
63
+ print("API 端点: /v1/completions, /v1/chat/completions")
64
+
65
+ # 使用 vLLM 内置的 OpenAI 兼容服务器
66
+ from vllm.entrypoints.openai.api_server import run_server
67
+ import uvicorn
68
+
69
+ uvicorn.run(
70
+ "vllm.entrypoints.openai.api_server:app",
71
+ host=host,
72
+ port=port,
73
+ log_level="info",
74
+ )
75
+
76
+
77
+ def test_inference(model_path="lxcxjxhx/Qwen3.5-0.8B-HOS"):
78
+ """推理性能测试"""
79
+ from vllm import LLM, SamplingParams
80
+
81
+ print(f"加载模型: {model_path}")
82
+ llm = load_model(model_path)
83
+
84
+ # 测试用例:信息安全领域问题
85
+ prompts = [
86
+ "请解释SQL注入攻击的原理和防御方法。",
87
+ "什么是XSS跨站脚本攻击?如何防范?",
88
+ "描述一次渗透测试的完整流程。",
89
+ "如何使用Wireshark进行网络流量分析?",
90
+ "解释RSA加密算法的工作原理。",
91
+ ]
92
+
93
+ sampling_params = SamplingParams(
94
+ temperature=0.7,
95
+ top_p=0.9,
96
+ max_tokens=256,
97
+ )
98
+
99
+ print(f"\n测试 {len(prompts)} 个请求...")
100
+ start = time.time()
101
+ outputs = llm.generate(prompts, sampling_params)
102
+ elapsed = time.time() - start
103
+
104
+ total_tokens = sum(len(o.outputs[0].token_ids) for o in outputs)
105
+ print(f"\n=== 推理测试结果 ===")
106
+ print(f"总耗时: {elapsed:.2f}s")
107
+ print(f"总生成 token 数: {total_tokens}")
108
+ print(f"吞吐量: {total_tokens / elapsed:.1f} tokens/s")
109
+ print(f"平均每请求: {elapsed / len(prompts):.2f}s")
110
+
111
+ for i, output in enumerate(outputs):
112
+ print(f"\n--- 请求 {i+1} ---")
113
+ print(f"Prompt: {output.prompt[:50]}...")
114
+ print(f"生成: {output.outputs[0].text[:200]}...")
115
+ print(f"Token 数: {len(output.outputs[0].token_ids)}")
116
+
117
+
118
+ def chat_mode(model_path="lxcxjxhx/Qwen3.5-0.8B-HOS"):
119
+ """命令行交互模式"""
120
+ from vllm import LLM, SamplingParams
121
+
122
+ print(f"加载模型: {model_path}")
123
+ llm = load_model(model_path)
124
+
125
+ sampling_params = SamplingParams(
126
+ temperature=0.7,
127
+ top_p=0.9,
128
+ max_tokens=512,
129
+ )
130
+
131
+ print("\n=== Qwen3.5-0.8B-HOS 交互模式 ===")
132
+ print("输入 'quit' 或 'exit' 退出\n")
133
+
134
+ while True:
135
+ try:
136
+ user_input = input("用户: ").strip()
137
+ if user_input.lower() in ("quit", "exit", "q"):
138
+ break
139
+ if not user_input:
140
+ continue
141
+
142
+ # 构造 chat template
143
+ prompt = f"<|im_start|>user\n{user_input}<|im_end|>\n<|im_start|>assistant\n"
144
+
145
+ start = time.time()
146
+ outputs = llm.generate([prompt], sampling_params)
147
+ elapsed = time.time() - start
148
+
149
+ response = outputs[0].outputs[0].text
150
+ token_count = len(outputs[0].outputs[0].token_ids)
151
+
152
+ print(f"\n助手: {response}")
153
+ print(f"[{token_count} tokens, {elapsed:.2f}s, {token_count/elapsed:.1f} tokens/s]\n")
154
+
155
+ except KeyboardInterrupt:
156
+ break
157
+
158
+ print("\n再见!")
159
+
160
+
161
+ if __name__ == "__main__":
162
+ parser = argparse.ArgumentParser(description="Qwen3.5-0.8B-HOS vLLM 推理服务")
163
+ parser.add_argument("--model", type=str, default="lxcxjxhx/Qwen3.5-0.8B-HOS",
164
+ help="模型路径或 HF 仓库 ID")
165
+ parser.add_argument("--dtype", type=str, default="bfloat16",
166
+ choices=["bfloat16", "float16", "float32"],
167
+ help="推理精度")
168
+ parser.add_argument("--serve", action="store_true", help="启动 API 服务")
169
+ parser.add_argument("--test", action="store_true", help="运行推理测试")
170
+ parser.add_argument("--chat", action="store_true", help="命令行交互模式")
171
+ parser.add_argument("--host", type=str, default="0.0.0.0", help="服务监听地址")
172
+ parser.add_argument("--port", type=int, default=8000, help="服务端口")
173
+
174
+ args = parser.parse_args()
175
+
176
+ if args.serve:
177
+ serve(args.model, args.host, args.port)
178
+ elif args.test:
179
+ test_inference(args.model)
180
+ elif args.chat:
181
+ chat_mode(args.model)
182
+ else:
183
+ # 默认启动服务
184
+ serve(args.model, args.host, args.port)
model.safetensors-00001-of-00001.safetensors DELETED
@@ -1,3 +0,0 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:c2b1e5a17d9c1e27685d92ed9b382911ebb99955ecd89052d1721241adfbab6c
3
- size 1746942600
 
 
 
 
requirements.txt ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Qwen3.5-0.8B-HOS 训练依赖
2
+ # 适用于 LLaMA-Factory QLoRA 训练流程
3
+
4
+ # 核心依赖
5
+ transformers>=4.40.0
6
+ tokenizers>=0.19.0
7
+ accelerate>=0.29.0
8
+ peft>=0.10.0
9
+ bitsandbytes>=0.43.0
10
+
11
+ # 数据处理
12
+ datasets>=2.18.0
13
+ huggingface-hub>=0.22.0
14
+
15
+ # 去重和清洗
16
+ datasketch>=1.6.5
17
+ simhash>=2.1.2
18
+
19
+ # 训练优化
20
+ einops>=0.7.0
21
+ scipy>=1.13.0
22
+
23
+ # 推理加速
24
+ vllm>=0.4.0
25
+
26
+ # 工具库
27
+ tqdm>=4.66.0
28
+ rich>=13.7.0
29
+
30
+ # 日志和监控
31
+ tensorboard>=2.16.0
run.sh ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ # 主入口脚本 - 串联所有训练步骤
3
+ # 适用于 Qwen3.5-0.8B-HOS 训练流程
4
+
5
+ set -e
6
+
7
+ echo "============================================"
8
+ echo " HOS Security Model Training"
9
+ echo " Qwen3.5-0.8B-HOS"
10
+ echo "============================================"
11
+
12
+ # 解析参数
13
+ NO_PROXY=false
14
+ for arg in "$@"; do
15
+ case $arg in
16
+ --no-proxy)
17
+ NO_PROXY=true
18
+ shift
19
+ ;;
20
+ esac
21
+ done
22
+
23
+ # Step 1: 代理配置(可选)
24
+ if [ "$NO_PROXY" = false ]; then
25
+ echo "[1/5] 配置网络代理..."
26
+ source scripts/setup_proxy.sh
27
+ else
28
+ echo "[1/5] 跳过代理配置"
29
+ fi
30
+
31
+ # Step 2: 安装依赖
32
+ echo "[2/5] 安装环境依赖..."
33
+ bash scripts/prepare_env.sh
34
+
35
+ # Step 3: 下载并清洗数据
36
+ echo "[3/5] 准备数据集..."
37
+ bash scripts/prepare_data.sh
38
+
39
+ # Step 4: 训练
40
+ echo "[4/5] 启动训练..."
41
+ bash scripts/train.sh
42
+
43
+ # Step 5: 上传
44
+ echo "[5/5] 上传模型..."
45
+ bash scripts/upload.sh
46
+
47
+ echo "============================================"
48
+ echo " 训练完成!"
49
+ echo " 模型: https://huggingface.co/lxcxjxhx/Qwen3.5-0.8B-HOS"
50
+ echo "============================================"
scripts/clean_data.py ADDED
@@ -0,0 +1,271 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ 数据清洗脚本 - 用于清洗和预处理网络安全训练数据
5
+ 适用于 Qwen3.5-0.8B-HOS 训练流程
6
+
7
+ 功能:
8
+ 1. 去重(MinHash + SimHash)
9
+ 2. 去噪(移除低质量数据)
10
+ 3. 格式标准化(统一为 Alpaca 格式)
11
+ 4. 隐私脱敏(移除敏感信息)
12
+ """
13
+
14
+ import json
15
+ import os
16
+ import re
17
+ import hashlib
18
+ from typing import Dict, List, Optional
19
+ from datasketch import MinHash, MinHashLSH
20
+ from simhash import Simhash
21
+
22
+
23
+ # 配置
24
+ DATA_DIR = "datasets"
25
+ OUTPUT_DIR = "datasets/cleaned"
26
+
27
+
28
+ def compute_minhash(text: str, num_perm: int = 128) -> MinHash:
29
+ """计算文本的 MinHash 签名"""
30
+ m = MinHash(num_perm=num_perm)
31
+ for word in text.split():
32
+ m.update(word.encode('utf-8'))
33
+ return m
34
+
35
+
36
+ def compute_simhash(text: str, hashbits: int = 64) -> Simhash:
37
+ """计算文本的 SimHash 指纹"""
38
+ return Simhash(text, hashbits=hashbits)
39
+
40
+
41
+ def deduplicate_minhash(data: List[Dict], threshold: float = 0.8) -> List[Dict]:
42
+ """使用 MinHash 进行去重"""
43
+ print(f" MinHash 去重 (阈值: {threshold})...")
44
+ lsh = MinHashLSH(threshold=threshold, num_perm=128)
45
+
46
+ unique_data = []
47
+ duplicates = 0
48
+
49
+ for idx, item in enumerate(data):
50
+ text = f"{item.get('instruction', '')} {item.get('input', '')} {item.get('output', '')}"
51
+ m = compute_minhash(text)
52
+
53
+ key = f"doc_{idx}"
54
+ if not lsh.query(m):
55
+ lsh.insert(key, m)
56
+ unique_data.append(item)
57
+ else:
58
+ duplicates += 1
59
+
60
+ print(f" 原始: {len(data)}, 去重后: {len(unique_data)}, 移除: {duplicates}")
61
+ return unique_data
62
+
63
+
64
+ def deduplicate_simhash(data: List[Dict], threshold: int = 3) -> List[Dict]:
65
+ """使用 SimHash 进行去重"""
66
+ print(f" SimHash 去重 (汉明距离阈值: {threshold})...")
67
+
68
+ unique_data = []
69
+ seen_hashes = []
70
+ duplicates = 0
71
+
72
+ for item in data:
73
+ text = f"{item.get('instruction', '')} {item.get('input', '')} {item.get('output', '')}"
74
+ sh = compute_simhash(text)
75
+
76
+ is_duplicate = False
77
+ for seen_sh in seen_hashes:
78
+ if sh.distance(seen_sh) <= threshold:
79
+ is_duplicate = True
80
+ duplicates += 1
81
+ break
82
+
83
+ if not is_duplicate:
84
+ seen_hashes.append(sh)
85
+ unique_data.append(item)
86
+
87
+ print(f" 原始: {len(data)}, 去重后: {len(unique_data)}, 移除: {duplicates}")
88
+ return unique_data
89
+
90
+
91
+ def remove_noise(data: List[Dict]) -> List[Dict]:
92
+ """去噪 - 移除低质量数据"""
93
+ print(" 去噪处理...")
94
+
95
+ clean_data = []
96
+ removed = 0
97
+
98
+ for item in data:
99
+ instruction = item.get('instruction', '')
100
+ input_text = item.get('input', '')
101
+ output = item.get('output', '')
102
+
103
+ # 过滤条件
104
+ # 1. 移除空内容
105
+ if not instruction or not output:
106
+ removed += 1
107
+ continue
108
+
109
+ # 2. 移除过短的内容
110
+ if len(output.strip()) < 10:
111
+ removed += 1
112
+ continue
113
+
114
+ # 3. 移除明显无关的内容
115
+ noise_patterns = [
116
+ r'^\s*$', # 纯空白
117
+ r'^(test|测试|demo|示例)\s*$', # 测试内容
118
+ r'^(TODO|FIXME|HACK)', # 代码注释
119
+ ]
120
+
121
+ is_noise = False
122
+ for pattern in noise_patterns:
123
+ if re.search(pattern, output, re.IGNORECASE):
124
+ is_noise = True
125
+ break
126
+
127
+ if is_noise:
128
+ removed += 1
129
+ continue
130
+
131
+ clean_data.append(item)
132
+
133
+ print(f" 原始: {len(data)}, 去噪后: {len(clean_data)}, 移除: {removed}")
134
+ return clean_data
135
+
136
+
137
+ def standardize_format(data: List[Dict]) -> List[Dict]:
138
+ """格式标准化 - 统一为 Alpaca 格式"""
139
+ print(" 格式标准化...")
140
+
141
+ standardized = []
142
+
143
+ for item in data:
144
+ # 确保必要的字段存在
145
+ standardized_item = {
146
+ 'instruction': item.get('instruction', '').strip(),
147
+ 'input': item.get('input', '').strip(),
148
+ 'output': item.get('output', '').strip()
149
+ }
150
+
151
+ # 清理多余空白
152
+ for key in standardized_item:
153
+ standardized_item[key] = re.sub(r'\s+', ' ', standardized_item[key]).strip()
154
+
155
+ standardized.append(standardized_item)
156
+
157
+ print(f" 标准化完成: {len(standardized)} 条记录")
158
+ return standardized
159
+
160
+
161
+ def desensitize(data: List[Dict]) -> List[Dict]:
162
+ """隐私脱敏 - 移除敏感信息"""
163
+ print(" 隐私脱敏...")
164
+
165
+ desensitized = []
166
+
167
+ # 敏感信息正则表达式
168
+ sensitive_patterns = {
169
+ 'email': r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b',
170
+ 'phone': r'\b1[3-9]\d{9}\b', # 中国大陆手机号
171
+ 'id_card': r'\b\d{17}[\dXx]\b', # 身份证号
172
+ 'ip': r'\b(?:\d{1,3}\.){3}\d{1,3}\b', # IP 地址
173
+ 'password': r'(password|密码|pwd)\s*[:=]\s*\S+', # 密码
174
+ 'api_key': r'(api[_-]?key|apikey)\s*[:=]\s*[\w-]+', # API Key
175
+ 'token': r'(token|access[_-]?token)\s*[:=]\s*[\w.-]+', # Token
176
+ }
177
+
178
+ for item in data:
179
+ new_item = item.copy()
180
+
181
+ for key in ['instruction', 'input', 'output']:
182
+ text = new_item.get(key, '')
183
+
184
+ # 替换敏感信息
185
+ for pattern_name, pattern in sensitive_patterns.items():
186
+ if pattern_name in ['email', 'phone', 'id_card']:
187
+ text = re.sub(pattern, f'<{pattern_name.upper()}>', text)
188
+ elif pattern_name in ['password', 'api_key', 'token']:
189
+ text = re.sub(pattern, f'<{pattern_name.upper()}>', text, flags=re.IGNORECASE)
190
+
191
+ new_item[key] = text
192
+
193
+ desensitized.append(new_item)
194
+
195
+ print(f" 脱敏完成: {len(desensitized)} 条记录")
196
+ return desensitized
197
+
198
+
199
+ def process_dataset(input_file: str, output_file: str):
200
+ """处理单个数据集文件"""
201
+ print(f"\n处理: {input_file}")
202
+
203
+ # 读取数据
204
+ data = []
205
+ with open(input_file, 'r', encoding='utf-8') as f:
206
+ for line in f:
207
+ if line.strip():
208
+ data.append(json.loads(line))
209
+
210
+ print(f" 原始数据: {len(data)} 条")
211
+
212
+ # 1. 去重 (MinHash)
213
+ data = deduplicate_minhash(data, threshold=0.8)
214
+
215
+ # 2. 去重 (SimHash)
216
+ data = deduplicate_simhash(data, threshold=3)
217
+
218
+ # 3. 去噪
219
+ data = remove_noise(data)
220
+
221
+ # 4. 格式标准化
222
+ data = standardize_format(data)
223
+
224
+ # 5. 隐私脱敏
225
+ data = desensitize(data)
226
+
227
+ # 保存结果
228
+ os.makedirs(os.path.dirname(output_file), exist_ok=True)
229
+ with open(output_file, 'w', encoding='utf-8') as f:
230
+ for item in data:
231
+ f.write(json.dumps(item, ensure_ascii=False) + '\n')
232
+
233
+ print(f" 处理后数据: {len(data)} 条")
234
+ print(f" 保存至: {output_file}")
235
+
236
+
237
+ def main():
238
+ """主函数"""
239
+ print("============================================")
240
+ print(" 数据清洗脚本")
241
+ print(" Qwen3.5-0.8B-HOS")
242
+ print("============================================")
243
+
244
+ # 创建输出目录
245
+ os.makedirs(OUTPUT_DIR, exist_ok=True)
246
+
247
+ # 处理所有数据集
248
+ datasets = [
249
+ 'cybersecurity_hq.jsonl',
250
+ 'cybersecurity_sharegpt.jsonl',
251
+ 'cnvd_vulnerability.jsonl',
252
+ 'merged_cybersec.jsonl'
253
+ ]
254
+
255
+ for dataset in datasets:
256
+ input_file = os.path.join(DATA_DIR, dataset)
257
+ output_file = os.path.join(OUTPUT_DIR, dataset)
258
+
259
+ if os.path.exists(input_file):
260
+ process_dataset(input_file, output_file)
261
+ else:
262
+ print(f"\n跳过: {input_file} (文件不存在)")
263
+
264
+ print("\n============================================")
265
+ print(" 数据清洗完成!")
266
+ print(f" 输出目录: {OUTPUT_DIR}")
267
+ print("============================================")
268
+
269
+
270
+ if __name__ == "__main__":
271
+ main()
scripts/prepare_data.sh ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ # 数据下载与清洗脚本
3
+ # 适用于 Qwen3.5-0.8B-HOS 训练流程
4
+
5
+ set -e
6
+
7
+ echo "============================================"
8
+ echo " 准备训练数据集"
9
+ echo "============================================"
10
+
11
+ # 数据集目录
12
+ DATA_DIR="datasets"
13
+ mkdir -p "$DATA_DIR"
14
+
15
+ # 从 HuggingFace 下载数据集
16
+ echo "[1/4] 下载数据集..."
17
+ python -c "
18
+ from datasets import load_dataset
19
+ from huggingface_hub import hf_hub_download
20
+ import os
21
+
22
+ # 下载 infosec-dataset-training 数据集
23
+ print('下载 cybersecurity_hq...')
24
+ ds = load_dataset('lxcxjxhx/infosec-dataset-training', 'cybersecurity_hq', trust_remote_code=True)
25
+ ds['train'].to_json(os.path.join('$DATA_DIR', 'cybersecurity_hq.jsonl'), orient='records', lines=True, force_ascii=False)
26
+
27
+ print('下载 cybersecurity_sharegpt...')
28
+ ds = load_dataset('lxcxjxhx/infosec-dataset-training', 'cybersecurity_sharegpt', trust_remote_code=True)
29
+ ds['train'].to_json(os.path.join('$DATA_DIR', 'cybersecurity_sharegpt.jsonl'), orient='records', lines=True, force_ascii=False)
30
+
31
+ print('下载 cnvd_vulnerability...')
32
+ ds = load_dataset('lxcxjxhx/infosec-dataset-training', 'cnvd_vulnerability', trust_remote_code=True)
33
+ ds['train'].to_json(os.path.join('$DATA_DIR', 'cnvd_vulnerability.jsonl'), orient='records', lines=True, force_ascii=False)
34
+
35
+ print('下载 merged_cybersec...')
36
+ ds = load_dataset('lxcxjxhx/infosec-dataset-training', 'merged_cybersec', trust_remote_code=True)
37
+ ds['train'].to_json(os.path.join('$DATA_DIR', 'merged_cybersec.jsonl'), orient='records', lines=True, force_ascii=False)
38
+
39
+ print('数据集下载完成!')
40
+ "
41
+
42
+ # 数据清洗
43
+ echo "[2/4] 数据清洗..."
44
+ python scripts/clean_data.py
45
+
46
+ # 验证数据集
47
+ echo "[3/4] 验证数据集..."
48
+ python -c "
49
+ import json
50
+ import os
51
+
52
+ data_dir = '$DATA_DIR'
53
+ files = ['cybersecurity_hq.jsonl', 'cybersecurity_sharegpt.jsonl', 'cnvd_vulnerability.jsonl', 'merged_cybersec.jsonl']
54
+
55
+ for f in files:
56
+ path = os.path.join(data_dir, f)
57
+ if os.path.exists(path):
58
+ with open(path, 'r', encoding='utf-8') as file:
59
+ count = sum(1 for _ in file)
60
+ print(f'{f}: {count} 条记录')
61
+ else:
62
+ print(f'{f}: 文件不存在!')
63
+ "
64
+
65
+ # 复制 dataset_info.json
66
+ echo "[4/4] 配置数据集信息..."
67
+ cp datasets/dataset_info.json "$DATA_DIR/dataset_info.json" 2>/dev/null || echo "dataset_info.json 已存在"
68
+
69
+ echo "============================================"
70
+ echo " 数据集准备完成!"
71
+ echo "============================================"
scripts/prepare_env.sh ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ # 环境安装脚本 - 安装训练所需的所有依赖
3
+ # 适用于 Qwen3.5-0.8B-HOS 训练流程
4
+
5
+ set -e
6
+
7
+ echo "============================================"
8
+ echo " 安装训练环境依赖"
9
+ echo "============================================"
10
+
11
+ # 检查 Python 版本
12
+ echo "[1/6] 检查 Python 版本..."
13
+ python --version
14
+
15
+ # 升级 pip
16
+ echo "[2/6] 升级 pip..."
17
+ python -m pip install --upgrade pip
18
+
19
+ # 安装 PyTorch (CUDA 12.1)
20
+ echo "[3/6] 安装 PyTorch..."
21
+ pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121
22
+
23
+ # 安装 LLaMA-Factory
24
+ echo "[4/6] 安装 LLaMA-Factory..."
25
+ pip install git+https://github.com/hiyouga/LLaMA-Factory.git
26
+
27
+ # 安装 Unsloth (加速训练)
28
+ echo "[5/6] 安装 Unsloth..."
29
+ pip install unsloth
30
+
31
+ # 安装 vLLM (推理加速)
32
+ echo "[6/6] 安装 vLLM..."
33
+ pip install vllm
34
+
35
+ # 安装其他依赖
36
+ echo "[7/7] 安装其他依赖..."
37
+ pip install -r requirements.txt
38
+
39
+ # 验证安装
40
+ echo "============================================"
41
+ echo " 验证安装..."
42
+ echo "============================================"
43
+ python -c "import torch; print(f'PyTorch: {torch.__version__}')"
44
+ python -c "import torch; print(f'CUDA Available: {torch.cuda.is_available()}')"
45
+ python -c "import torch; print(f'CUDA Version: {torch.version.cuda}')"
46
+ python -c "import transformers; print(f'Transformers: {transformers.__version__}')"
47
+
48
+ echo "============================================"
49
+ echo " 环境安装完成!"
50
+ echo "============================================"
scripts/setup_proxy.sh ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ # 代理配置脚本 - 配置网络代理以访问外部资源
3
+ # 适用于 Qwen3.5-0.8B-HOS 训练流程
4
+
5
+ echo "============================================"
6
+ echo " 配置网络代理 (端口 7897)"
7
+ echo "============================================"
8
+
9
+ # 设置代理环境变量
10
+ export http_proxy=http://127.0.0.1:7897
11
+ export https_proxy=http://127.0.0.1:7897
12
+ export HTTP_PROXY=http://127.0.0.1:7897
13
+ export HTTPS_PROXY=http://127.0.0.1:7897
14
+
15
+ # Git 代理配置
16
+ git config --global http.proxy http://127.0.0.1:7897
17
+ git config --global https.proxy http://127.0.0.1:7897
18
+
19
+ # HuggingFace 代理配置
20
+ export HF_HUB_ENABLE_HF_TRANSFER=1
21
+
22
+ echo "代理配置完成:"
23
+ echo " HTTP_PROXY: $http_proxy"
24
+ echo " HTTPS_PROXY: $https_proxy"
25
+ echo "============================================"
scripts/train.sh ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ # 训练启动脚本 - 使用 LLaMA-Factory 进行 QLoRA 训练
3
+ # 适用于 Qwen3.5-0.8B-HOS 训练流程
4
+
5
+ set -e
6
+
7
+ echo "============================================"
8
+ echo " 启动 QLoRA 训练"
9
+ echo " 模型: Qwen3.5-0.8B-HOS"
10
+ echo "============================================"
11
+
12
+ # 检查配置文件
13
+ CONFIG_FILE="configs/train.yaml"
14
+ if [ ! -f "$CONFIG_FILE" ]; then
15
+ echo "错误: 配置文件不存在: $CONFIG_FILE"
16
+ exit 1
17
+ fi
18
+
19
+ # 检查数据集
20
+ DATASET_DIR="datasets"
21
+ if [ ! -d "$DATASET_DIR" ]; then
22
+ echo "错误: 数据集目录不存在: $DATASET_DIR"
23
+ exit 1
24
+ fi
25
+
26
+ # 创建输出目录
27
+ OUTPUT_DIR="outputs/qwen35-0.8b-cybersec-qlora"
28
+ mkdir -p "$OUTPUT_DIR"
29
+
30
+ # 显示训练配置
31
+ echo "训练配置:"
32
+ echo " 配置文件: $CONFIG_FILE"
33
+ echo " 数据集目录: $DATASET_DIR"
34
+ echo " 输出目录: $OUTPUT_DIR"
35
+ echo ""
36
+
37
+ # 显示系统信息
38
+ echo "系统信息:"
39
+ python -c "import torch; print(f' PyTorch: {torch.__version__}')"
40
+ python -c "import torch; print(f' CUDA Available: {torch.cuda.is_available()}')"
41
+ if python -c "import torch; exit(0 if torch.cuda.is_available() else 1)" 2>/dev/null; then
42
+ python -c "import torch; print(f' GPU: {torch.cuda.get_device_name(0)}')"
43
+ python -c "import torch; print(f' GPU Memory: {torch.cuda.get_device_properties(0).total_memory / 1024**3:.2f} GB')"
44
+ fi
45
+ echo ""
46
+
47
+ # 开始训练
48
+ echo "开始训练..."
49
+ echo "============================================"
50
+
51
+ llamafactory-cli train "$CONFIG_FILE"
52
+
53
+ echo "============================================"
54
+ echo " 训练完成!"
55
+ echo " 模型输出: $OUTPUT_DIR"
56
+ echo "============================================"
scripts/upload.sh ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ # 上传脚本 - 合并 adapter 并上传到 HuggingFace Hub
3
+ # 适用于 Qwen3.5-0.8B-HOS 训练流程
4
+
5
+ set -e
6
+
7
+ echo "============================================"
8
+ echo " 上传模型到 HuggingFace Hub"
9
+ echo " 目标: lxcxjxhx/Qwen3.5-0.8B-HOS"
10
+ echo "============================================"
11
+
12
+ # 配置
13
+ OUTPUT_DIR="outputs/qwen35-0.8b-cybersec-qlora"
14
+ MERGED_DIR="outputs/qwen35-0.8b-merged"
15
+ HUB_MODEL_ID="lxcxjxhx/Qwen3.5-0.8B-HOS"
16
+
17
+ # 检查训练输出
18
+ if [ ! -d "$OUTPUT_DIR" ]; then
19
+ echo "错误: 训练输出目录不存在: $OUTPUT_DIR"
20
+ exit 1
21
+ fi
22
+
23
+ # Step 1: 合并 adapter
24
+ echo "[1/3] 合并 LoRA adapter..."
25
+ python -c "
26
+ from transformers import AutoModelForCausalLM, AutoTokenizer
27
+ from peft import PeftModel
28
+ import torch
29
+
30
+ print('加载基础模型...')
31
+ base_model = AutoModelForCausalLM.from_pretrained(
32
+ 'lxcxjxhx/Qwen3.5-0.8B-HOS',
33
+ torch_dtype=torch.bfloat16,
34
+ trust_remote_code=True
35
+ )
36
+
37
+ print('加载 LoRA adapter...')
38
+ model = PeftModel.from_pretrained(base_model, '$OUTPUT_DIR')
39
+
40
+ print('合并模型...')
41
+ model = model.merge_and_unload()
42
+
43
+ print('保存合并后的模型...')
44
+ model.save_pretrained('$MERGED_DIR')
45
+
46
+ print('保存 tokenizer...')
47
+ tokenizer = AutoTokenizer.from_pretrained('lxcxjxhx/Qwen3.5-0.8B-HOS', trust_remote_code=True)
48
+ tokenizer.save_pretrained('$MERGED_DIR')
49
+
50
+ print('合并完成!')
51
+ "
52
+
53
+ # Step 2: 推送到 HuggingFace Hub
54
+ echo "[2/3] 推送到 HuggingFace Hub..."
55
+ python -c "
56
+ from huggingface_hub import HfApi
57
+
58
+ api = HfApi()
59
+
60
+ print('上传模型到: $HUB_MODEL_ID')
61
+ api.upload_folder(
62
+ folder_path='$MERGED_DIR',
63
+ repo_id='$HUB_MODEL_ID',
64
+ repo_type='model',
65
+ commit_message='Upload Qwen3.5-0.8B-HOS merged model'
66
+ )
67
+
68
+ print('上传完成!')
69
+ "
70
+
71
+ # Step 3: 验证上传
72
+ echo "[3/3] 验证上传..."
73
+ python -c "
74
+ from huggingface_hub import model_info
75
+
76
+ info = model_info('$HUB_MODEL_ID')
77
+ print(f'模型: {info.modelId}')
78
+ print(f'最后更新: {info.lastModified}')
79
+ print(f'标签: {info.tags}')
80
+ print('验证完成!')
81
+ "
82
+
83
+ echo "============================================"
84
+ echo " 上传完成!"
85
+ echo " 模型地址: https://huggingface.co/$HUB_MODEL_ID"
86
+ echo "============================================"