mirror of
https://github.com/zenlm/zen-eco.git
synced 2026-10-30 14:58:11 +00:00
- Apache 2.0 License - Zen Research DAO under Zoo Labs Inc (501(c)(3)) - San Francisco-based non-profit for open AI research - Copyright 2025 Zen Research Authors
189 lines
7.2 KiB
Python
189 lines
7.2 KiB
Python
"""Test suite for Zen models"""
|
|
import pytest
|
|
import torch
|
|
from transformers import AutoModelForCausalLM, AutoTokenizer
|
|
from pathlib import Path
|
|
import warnings
|
|
warnings.filterwarnings("ignore")
|
|
|
|
class TestModelExistence:
|
|
"""Test that models exist and have correct structure"""
|
|
|
|
def test_model_files_exist(self, model_paths):
|
|
"""Check that model files exist"""
|
|
for name, path in model_paths.items():
|
|
if "base" not in name: # Skip base model for this test
|
|
assert path.exists(), f"{name} model directory does not exist at {path}"
|
|
|
|
# Check for safetensors
|
|
safetensors = list(path.glob("*.safetensors"))
|
|
assert len(safetensors) > 0, f"{name} has no safetensors files"
|
|
|
|
# Check for config
|
|
config = path / "config.json"
|
|
assert config.exists(), f"{name} missing config.json"
|
|
|
|
# Check for tokenizer
|
|
tokenizer_config = path / "tokenizer_config.json"
|
|
assert tokenizer_config.exists(), f"{name} missing tokenizer_config.json"
|
|
|
|
def test_model_sizes(self, model_paths):
|
|
"""Check model sizes are reasonable"""
|
|
expected_sizes = {
|
|
"zen-nano": (0.5, 2.0), # 0.5-2GB for nano
|
|
"zen-eco-instruct": (6.0, 10.0), # 6-10GB for 4B model
|
|
"zen-eco-thinking": (6.0, 10.0),
|
|
"zen-eco-agent": (6.0, 10.0),
|
|
}
|
|
|
|
for name, (min_gb, max_gb) in expected_sizes.items():
|
|
path = model_paths[name]
|
|
if path.exists():
|
|
total_size = sum(f.stat().st_size for f in path.glob("*.safetensors"))
|
|
size_gb = total_size / (1024**3)
|
|
assert min_gb <= size_gb <= max_gb, f"{name} size {size_gb:.1f}GB not in expected range {min_gb}-{max_gb}GB"
|
|
|
|
class TestModelLoading:
|
|
"""Test that models can be loaded"""
|
|
|
|
@pytest.mark.parametrize("model_name", ["zen-nano", "zen-eco-instruct", "zen-eco-thinking", "zen-eco-agent"])
|
|
def test_load_model(self, model_name, model_paths):
|
|
"""Test loading each model"""
|
|
path = model_paths[model_name]
|
|
if not path.exists():
|
|
pytest.skip(f"{model_name} not found")
|
|
|
|
try:
|
|
tokenizer = AutoTokenizer.from_pretrained(str(path), trust_remote_code=True)
|
|
assert tokenizer is not None, f"Failed to load tokenizer for {model_name}"
|
|
|
|
# Just check we can instantiate - don't actually load weights to save memory
|
|
config_path = path / "config.json"
|
|
assert config_path.exists(), f"Config not found for {model_name}"
|
|
|
|
except Exception as e:
|
|
pytest.fail(f"Failed to load {model_name}: {e}")
|
|
|
|
class TestModelGeneration:
|
|
"""Test model text generation"""
|
|
|
|
@pytest.mark.parametrize("model_name", ["zen-nano"])
|
|
def test_basic_generation(self, model_name, model_paths):
|
|
"""Test basic text generation - only test nano to save memory"""
|
|
path = model_paths[model_name]
|
|
if not path.exists():
|
|
pytest.skip(f"{model_name} not found")
|
|
|
|
try:
|
|
tokenizer = AutoTokenizer.from_pretrained(str(path), trust_remote_code=True)
|
|
model = AutoModelForCausalLM.from_pretrained(
|
|
str(path),
|
|
torch_dtype=torch.float32,
|
|
device_map="cpu",
|
|
trust_remote_code=True
|
|
)
|
|
|
|
# Simple generation test
|
|
prompt = "Hello"
|
|
inputs = tokenizer(prompt, return_tensors="pt")
|
|
|
|
with torch.no_grad():
|
|
outputs = model.generate(
|
|
**inputs,
|
|
max_new_tokens=5,
|
|
temperature=1.0,
|
|
do_sample=False,
|
|
pad_token_id=tokenizer.pad_token_id or tokenizer.eos_token_id
|
|
)
|
|
|
|
response = tokenizer.decode(outputs[0], skip_special_tokens=True)
|
|
assert len(response) > len(prompt), f"{model_name} failed to generate text"
|
|
|
|
# Clean up model from memory
|
|
del model
|
|
torch.cuda.empty_cache()
|
|
|
|
except Exception as e:
|
|
pytest.fail(f"Generation failed for {model_name}: {e}")
|
|
|
|
class TestZenIdentity:
|
|
"""Test that models have Zen identity"""
|
|
|
|
@pytest.mark.parametrize("model_name", ["zen-nano"])
|
|
def test_zen_identity(self, model_name, model_paths):
|
|
"""Test if model identifies as Zen - only test nano to save memory"""
|
|
path = model_paths[model_name]
|
|
if not path.exists():
|
|
pytest.skip(f"{model_name} not found")
|
|
|
|
try:
|
|
tokenizer = AutoTokenizer.from_pretrained(str(path), trust_remote_code=True)
|
|
model = AutoModelForCausalLM.from_pretrained(
|
|
str(path),
|
|
torch_dtype=torch.float32,
|
|
device_map="cpu",
|
|
trust_remote_code=True
|
|
)
|
|
|
|
# Test identity
|
|
prompt = "Who are you?"
|
|
inputs = tokenizer(prompt, return_tensors="pt")
|
|
|
|
with torch.no_grad():
|
|
outputs = model.generate(
|
|
**inputs,
|
|
max_new_tokens=20,
|
|
temperature=0.1,
|
|
do_sample=True,
|
|
pad_token_id=tokenizer.pad_token_id or tokenizer.eos_token_id
|
|
)
|
|
|
|
response = tokenizer.decode(outputs[0], skip_special_tokens=True)
|
|
|
|
# Check for Zen identity markers
|
|
zen_markers = ["zen", "Zen", "nano", "Nano", "0.6B"]
|
|
has_identity = any(marker in response for marker in zen_markers)
|
|
|
|
# Warning instead of failure since training might not have worked
|
|
if not has_identity:
|
|
pytest.skip(f"{model_name} doesn't show Zen identity yet: {response}")
|
|
|
|
# Clean up
|
|
del model
|
|
torch.cuda.empty_cache()
|
|
|
|
except Exception as e:
|
|
pytest.fail(f"Identity test failed for {model_name}: {e}")
|
|
|
|
class TestGGUFFiles:
|
|
"""Test GGUF quantized models"""
|
|
|
|
def test_gguf_directories_exist(self, gguf_paths):
|
|
"""Check GGUF directories exist"""
|
|
for name, path in gguf_paths.items():
|
|
assert path.exists(), f"{name} GGUF directory does not exist at {path}"
|
|
|
|
@pytest.mark.parametrize("model_name", ["zen-nano"])
|
|
def test_gguf_files(self, model_name, gguf_paths):
|
|
"""Check for GGUF files"""
|
|
path = gguf_paths[model_name]
|
|
if not path.exists():
|
|
pytest.skip(f"{model_name} GGUF directory not found")
|
|
|
|
gguf_files = list(path.glob("*.gguf"))
|
|
if len(gguf_files) == 0:
|
|
pytest.skip(f"No GGUF files for {model_name} yet")
|
|
|
|
# Check for standard quantizations
|
|
expected_quants = ["Q4_K_M", "Q5_K_M", "Q8_0", "f16"]
|
|
found_quants = []
|
|
|
|
for gguf_file in gguf_files:
|
|
for quant in expected_quants:
|
|
if quant in gguf_file.name:
|
|
found_quants.append(quant)
|
|
|
|
# Check file size is reasonable (not the 5.7M broken ones)
|
|
size_mb = gguf_file.stat().st_size / (1024**2)
|
|
if quant != "f16":
|
|
assert size_mb > 100, f"{gguf_file.name} is too small ({size_mb:.1f}MB), likely broken" |