zhan1206 commited on
Commit
656dacb
·
1 Parent(s): 0dafe0f

fix(v12): resolve 6 defects from v11 audit (F-NEW-6, S-NEW-5, M-NEW-5/6, MI-NEW-4/5/6)

Browse files

FATAL:
- F-NEW-6: QATTrainer.prepare() crashes on load_model() returning None -> add None guard

SERIOUS:
- S-NEW-5: save() uses HF save_pretrained which can't serialize QuantizedLinear -> switch to safetensors/torch.save

MODERATE:
- M-NEW-5: _insert_fake_quant only matches LLaMA layer names -> apply qconfig to all nn.Linear
- M-NEW-6: ollama_deploy_v2 fallback export ignores sharded models -> detect index.json and merge shards

MINOR:
- MI-NEW-4: manage_mini_data.py DATA_PATH relative -> use Path(__file__).resolve().parent.parent
- MI-NEW-5: bilingual_filter/ollama_deploy/fusion_mini/run_tests [LOGO] residue -> replace with [INFO]/[WARN]
- MI-NEW-6: ollama_deploy.py check_dependencies lacks shell=True on Windows -> add shell=True
- also fix broken string literal in ollama_deploy.py line 46

data_pipeline/bilingual_filter.py CHANGED
@@ -377,7 +377,7 @@ def process_data_pipeline(
377
  en_clean = en_filter.process(en_raw)
378
 
379
  # 4. 平衡采样
380
- logger.info("\n[BALANCE][LOGO] 平衡采样...")
381
  sampler = BalancedSampler(zh_clean, en_clean, zh_ratio=0.5)
382
  balanced_data = sampler.sample(n_samples)
383
 
@@ -393,7 +393,7 @@ def process_data_pipeline(
393
 
394
  if __name__ == "__main__":
395
  # 单元测试(模拟数据)
396
- print("[LOGO] 测试 Bi-Lingual TrueFilter...")
397
 
398
  # 模拟中文数据
399
  zh_test_data = [
 
377
  en_clean = en_filter.process(en_raw)
378
 
379
  # 4. 平衡采样
380
+ logger.info("\n[BALANCE] 平衡采样...")
381
  sampler = BalancedSampler(zh_clean, en_clean, zh_ratio=0.5)
382
  balanced_data = sampler.sample(n_samples)
383
 
 
393
 
394
  if __name__ == "__main__":
395
  # 单元测试(模拟数据)
396
+ print("[TEST] 测试 Bi-Lingual TrueFilter...")
397
 
398
  # 模拟中文数据
399
  zh_test_data = [
inference/dyquant.py CHANGED
@@ -520,8 +520,22 @@ class DyQuantConverter:
520
  output_dir = Path(output_path)
521
  output_dir.mkdir(parents=True, exist_ok=True)
522
 
523
- # 保存模型
524
- self.model.save_pretrained(output_dir)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
525
 
526
  # 保存量化配置
527
  quant_config = {
@@ -639,19 +653,20 @@ class QATTrainer:
639
  self.model = None
640
  self.qat_model = None
641
 
642
- def prepare(self) -> nn.Module:
643
  """Load model and insert fake-quantization nodes."""
644
  self.model = self.converter.load_model()
 
 
 
645
  self.qat_model = self._insert_fake_quant(self.model)
646
  return self.qat_model
647
 
648
  def _insert_fake_quant(self, model: nn.Module) -> nn.Module:
649
  """Insert fake-quantization observers into all Linear layers."""
650
  for name, module in model.named_modules():
651
- if isinstance(module, nn.Linear) and any(
652
- kw in name for kw in ['q_proj', 'k_proj', 'v_proj', 'out_proj', 'gate_proj', 'up_proj', 'down_proj']
653
- ):
654
- # Use PyTorch native fake quantization per-module
655
  module.qconfig = torch.ao.quantization.get_default_qat_qconfig('x86')
656
  torch.ao.quantization.prepare_qat(model, inplace=True)
657
  return model
 
520
  output_dir = Path(output_path)
521
  output_dir.mkdir(parents=True, exist_ok=True)
522
 
523
+ # 保存模型 - extract state_dict from custom quantized layers
524
+ # Custom QuantizedLinear layers are not HF-compatible, use safetensors directly
525
+ try:
526
+ import safetensors.torch as st
527
+ state = self.model.state_dict()
528
+ # Convert non-tensor values (scales/zeros) to tensors for serialization
529
+ clean_state = {}
530
+ for k, v in state.items():
531
+ if isinstance(v, torch.Tensor):
532
+ clean_state[k] = v.contiguous()
533
+ else:
534
+ clean_state[k] = torch.tensor(v) if v is not None else torch.tensor(0.0)
535
+ st.save_file(clean_state, str(output_dir / "model.safetensors"))
536
+ except ImportError:
537
+ # Fallback to torch.save if safetensors unavailable
538
+ torch.save(self.model.state_dict(), output_dir / "pytorch_model.bin")
539
 
540
  # 保存量化配置
541
  quant_config = {
 
653
  self.model = None
654
  self.qat_model = None
655
 
656
+ def prepare(self) -> Optional[nn.Module]:
657
  """Load model and insert fake-quantization nodes."""
658
  self.model = self.converter.load_model()
659
+ if self.model is None:
660
+ print("[DyQuant] QAT: model load failed, cannot prepare")
661
+ return None
662
  self.qat_model = self._insert_fake_quant(self.model)
663
  return self.qat_model
664
 
665
  def _insert_fake_quant(self, model: nn.Module) -> nn.Module:
666
  """Insert fake-quantization observers into all Linear layers."""
667
  for name, module in model.named_modules():
668
+ if isinstance(module, nn.Linear):
669
+ # Use PyTorch native fake quantization for all Linear layers
 
 
670
  module.qconfig = torch.ao.quantization.get_default_qat_qconfig('x86')
671
  torch.ao.quantization.prepare_qat(model, inplace=True)
672
  return model
inference/ollama_deploy.py CHANGED
@@ -41,9 +41,9 @@ def check_dependencies():
41
  convert_script = os.path.join(llama_cpp_dir, "convert-hf-to-gguf.py")
42
 
43
  if not os.path.exists(convert_script):
44
- logger.warning(f"[WARN][LOGO] 未找到 llama.cpp 转换脚本:{convert_script}")
45
  logger.warning(" 请设置环境变量 LLAMA_CPP_DIR 或手动下载 llama.cpp")
46
- logger.warning(" 下载地址:<ADDRESS_REMOVED>
47
  return False
48
 
49
  # 检查 Ollama
@@ -52,15 +52,16 @@ def check_dependencies():
52
  ["ollama", "--version"],
53
  capture_output=True,
54
  text=True,
 
55
  )
56
  if result.returncode == 0:
57
  logger.info(f"[OK] Ollama 已安装:{result.stdout.strip()}")
58
  else:
59
- logger.warning("[WARN][LOGO] Ollama 未安装或无法运行")
60
  logger.warning(" 请访问 https://ollama.com 安装")
61
  return False
62
  except FileNotFoundError:
63
- logger.warning("[WARN][LOGO] Ollama 未安装")
64
  logger.warning(" 请访问 https://ollama.com 安装")
65
  return False
66
 
@@ -118,7 +119,7 @@ def convert_to_gguf(
118
  result = subprocess.run(quantize_cmd, capture_output=True, text=True)
119
 
120
  if result.returncode != 0:
121
- logger.warning(f"[WARN][LOGO] 量化失败:{result.stderr}")
122
  logger.warning(" 继续使用未量化模型")
123
  else:
124
  output_path = output_path.replace(".gguf", f"_{quantize}.gguf")
@@ -392,7 +393,7 @@ ollama run {model_name} --top_p 0.95
392
  with open(output_path, 'w', encoding='utf-8') as f:
393
  f.write(content)
394
 
395
- logger.info(f"[LOGO] 使用示例已生成:{output_path}")
396
 
397
 
398
  def main():
 
41
  convert_script = os.path.join(llama_cpp_dir, "convert-hf-to-gguf.py")
42
 
43
  if not os.path.exists(convert_script):
44
+ logger.warning(f"[WARN] 未找到 llama.cpp 转换脚本:{convert_script}")
45
  logger.warning(" 请设置环境变量 LLAMA_CPP_DIR 或手动下载 llama.cpp")
46
+ logger.warning(" https://github.com/ggerganov/llama.cpp")
47
  return False
48
 
49
  # 检查 Ollama
 
52
  ["ollama", "--version"],
53
  capture_output=True,
54
  text=True,
55
+ shell=True,
56
  )
57
  if result.returncode == 0:
58
  logger.info(f"[OK] Ollama 已安装:{result.stdout.strip()}")
59
  else:
60
+ logger.warning("[WARN] Ollama 未安装或无法运行")
61
  logger.warning(" 请访问 https://ollama.com 安装")
62
  return False
63
  except FileNotFoundError:
64
+ logger.warning("[WARN] Ollama 未安装")
65
  logger.warning(" 请访问 https://ollama.com 安装")
66
  return False
67
 
 
119
  result = subprocess.run(quantize_cmd, capture_output=True, text=True)
120
 
121
  if result.returncode != 0:
122
+ logger.warning(f"[WARN] 量化失败:{result.stderr}")
123
  logger.warning(" 继续使用未量化模型")
124
  else:
125
  output_path = output_path.replace(".gguf", f"_{quantize}.gguf")
 
393
  with open(output_path, 'w', encoding='utf-8') as f:
394
  f.write(content)
395
 
396
+ logger.info(f"[INFO] 使用示例已生成:{output_path}")
397
 
398
 
399
  def main():
inference/ollama_deploy_v2.py CHANGED
@@ -557,16 +557,34 @@ def _fallback_export_gguf(model_path: str, output_path: str) -> Optional[str]:
557
  config = FusionConfig.from_pretrained(model_path)
558
  model = FusionModel(config)
559
 
560
- # Load weights
561
- from pathlib import Path
562
- weight_files = list(Path(model_path).glob("*.safetensors")) + list(Path(model_path).glob("*.bin"))
563
- if not weight_files:
564
- logger.error("No model weight files found")
565
- return None
566
-
567
- # Export as safetensors
568
  export_path = output_path.replace('.gguf', '.safetensors')
569
- st.save_file(model.state_dict(), export_path)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
570
  logger.info(f"Exported model weights to: {export_path}")
571
  logger.info("NOTE: This is a safetensors export, not GGUF. For Ollama deployment,")
572
  logger.info(" convert this to GGUF using llama.cpp after ensuring architecture compatibility.")
 
557
  config = FusionConfig.from_pretrained(model_path)
558
  model = FusionModel(config)
559
 
560
+ # Export path
 
 
 
 
 
 
 
561
  export_path = output_path.replace('.gguf', '.safetensors')
562
+
563
+ # Load weights - handle sharded models (index.json + multiple safetensors)
564
+ from pathlib import Path
565
+ model_path_obj = Path(model_path)
566
+ index_file = model_path_obj / "model.safetensors.index.json"
567
+
568
+ if index_file.exists():
569
+ # Sharded model: load all shards and merge
570
+ import json as _json
571
+ with open(index_file, 'r') as f:
572
+ index = _json.load(f)
573
+ weight_map = index.get("weight_map", {})
574
+ shard_files = set(weight_map.values())
575
+ merged_state = {}
576
+ for shard in shard_files:
577
+ shard_path = model_path_obj / shard
578
+ shard_state = st.load_file(str(shard_path))
579
+ merged_state.update(shard_state)
580
+ st.save_file(merged_state, export_path)
581
+ else:
582
+ # Single-file model
583
+ weight_files = list(model_path_obj.glob("*.safetensors")) + list(model_path_obj.glob("*.bin"))
584
+ if not weight_files:
585
+ logger.error("No model weight files found")
586
+ return None
587
+ st.save_file(model.state_dict(), export_path)
588
  logger.info(f"Exported model weights to: {export_path}")
589
  logger.info("NOTE: This is a safetensors export, not GGUF. For Ollama deployment,")
590
  logger.info(" convert this to GGUF using llama.cpp after ensuring architecture compatibility.")
models/fusion_mini.py CHANGED
@@ -481,7 +481,7 @@ class FusionMini(PreTrainedModel):
481
 
482
  if __name__ == "__main__":
483
  # 单元测试
484
- print("[LOGO] 测试 Fusion Mini 模型...")
485
 
486
  # 创建配置
487
  config = FusionMiniConfig(
 
481
 
482
  if __name__ == "__main__":
483
  # 单元测试
484
+ print("[INFO] 测试 Fusion Mini 模型...")
485
 
486
  # 创建配置
487
  config = FusionMiniConfig(
scripts/manage_mini_data.py CHANGED
@@ -14,7 +14,7 @@ import re
14
  import sys
15
  from pathlib import Path
16
 
17
- DATA_PATH = Path("data/mini_data.json")
18
 
19
  # --- Depth assignment keywords ---
20
  DEPTH_3_KEYWORDS = ['prove', 'theorem', 'proof', 'derive', 'mathematical', 'complex',
 
14
  import sys
15
  from pathlib import Path
16
 
17
+ DATA_PATH = Path(__file__).resolve().parent.parent / "data" / "mini_data.json"
18
 
19
  # --- Depth assignment keywords ---
20
  DEPTH_3_KEYWORDS = ['prove', 'theorem', 'proof', 'derive', 'mathematical', 'complex',
tests/run_tests.py CHANGED
@@ -24,7 +24,7 @@ from pathlib import Path
24
  project_root = Path(__file__).parent.parent
25
  sys.path.insert(0, str(project_root))
26
 
27
- print("[LOGO] Fusion 项目单元测试")
28
  print("=" * 50)
29
 
30
 
 
24
  project_root = Path(__file__).parent.parent
25
  sys.path.insert(0, str(project_root))
26
 
27
+ print("[INFO] Fusion 项目单元测试")
28
  print("=" * 50)
29
 
30