Spaces:
Running
fix(v12): resolve 6 defects from v11 audit (F-NEW-6, S-NEW-5, M-NEW-5/6, MI-NEW-4/5/6)
Browse filesFATAL:
- 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 +2 -2
- inference/dyquant.py +22 -7
- inference/ollama_deploy.py +7 -6
- inference/ollama_deploy_v2.py +27 -9
- models/fusion_mini.py +1 -1
- scripts/manage_mini_data.py +1 -1
- tests/run_tests.py +1 -1
|
@@ -377,7 +377,7 @@ def process_data_pipeline(
|
|
| 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,7 +393,7 @@ def process_data_pipeline(
|
|
| 393 |
|
| 394 |
if __name__ == "__main__":
|
| 395 |
# 单元测试(模拟数据)
|
| 396 |
-
print("[
|
| 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 = [
|
|
@@ -520,8 +520,22 @@ class DyQuantConverter:
|
|
| 520 |
output_dir = Path(output_path)
|
| 521 |
output_dir.mkdir(parents=True, exist_ok=True)
|
| 522 |
|
| 523 |
-
# 保存模型
|
| 524 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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)
|
| 652 |
-
|
| 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
|
|
@@ -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]
|
| 45 |
logger.warning(" 请设置环境变量 LLAMA_CPP_DIR 或手动下载 llama.cpp")
|
| 46 |
-
logger.warning("
|
| 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]
|
| 60 |
logger.warning(" 请访问 https://ollama.com 安装")
|
| 61 |
return False
|
| 62 |
except FileNotFoundError:
|
| 63 |
-
logger.warning("[WARN]
|
| 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]
|
| 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"[
|
| 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():
|
|
@@ -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 |
-
#
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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.")
|
|
@@ -481,7 +481,7 @@ class FusionMini(PreTrainedModel):
|
|
| 481 |
|
| 482 |
if __name__ == "__main__":
|
| 483 |
# 单元测试
|
| 484 |
-
print("[
|
| 485 |
|
| 486 |
# 创建配置
|
| 487 |
config = FusionMiniConfig(
|
|
|
|
| 481 |
|
| 482 |
if __name__ == "__main__":
|
| 483 |
# 单元测试
|
| 484 |
+
print("[INFO] 测试 Fusion Mini 模型...")
|
| 485 |
|
| 486 |
# 创建配置
|
| 487 |
config = FusionMiniConfig(
|
|
@@ -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',
|
|
@@ -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("[
|
| 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 |
|