mirror of
https://github.com/zenlm/zen-musician.git
synced 2026-10-30 14:57:51 +00:00
176 lines
6.0 KiB
Python
176 lines
6.0 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Zen Musician Inference Tests
|
|
Tests basic inference functionality for the music generation model.
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import pytest
|
|
import torch
|
|
from pathlib import Path
|
|
|
|
# Add inference directory to path
|
|
sys.path.insert(0, str(Path(__file__).parent.parent / "inference"))
|
|
|
|
from transformers import AutoModelForCausalLM, AutoTokenizer
|
|
|
|
|
|
class TestZenMusicianInference:
|
|
"""Test suite for Zen Musician inference"""
|
|
|
|
@pytest.fixture(scope="class")
|
|
def model_path(self):
|
|
"""Get model path from environment or use default"""
|
|
return os.getenv("ZEN_MUSICIAN_MODEL", "m-a-p/YuE-s1-7B-anneal-en-cot")
|
|
|
|
@pytest.fixture(scope="class")
|
|
def model_and_tokenizer(self, model_path):
|
|
"""Load model and tokenizer once for all tests"""
|
|
print(f"Loading model: {model_path}")
|
|
tokenizer = AutoTokenizer.from_pretrained(model_path)
|
|
model = AutoModelForCausalLM.from_pretrained(
|
|
model_path,
|
|
torch_dtype=torch.float16,
|
|
device_map="auto",
|
|
low_cpu_mem_usage=True
|
|
)
|
|
return model, tokenizer
|
|
|
|
def test_model_loading(self, model_and_tokenizer):
|
|
"""Test that model and tokenizer load successfully"""
|
|
model, tokenizer = model_and_tokenizer
|
|
assert model is not None, "Model failed to load"
|
|
assert tokenizer is not None, "Tokenizer failed to load"
|
|
assert model.config.model_type in ["qwen2", "llama"], "Unexpected model type"
|
|
|
|
def test_tokenizer_basics(self, model_and_tokenizer):
|
|
"""Test basic tokenizer functionality"""
|
|
_, tokenizer = model_and_tokenizer
|
|
|
|
text = "Hello, Zen Musician!"
|
|
tokens = tokenizer.encode(text)
|
|
|
|
assert len(tokens) > 0, "Tokenization produced no tokens"
|
|
assert isinstance(tokens, list), "Tokens should be a list"
|
|
|
|
decoded = tokenizer.decode(tokens)
|
|
assert decoded is not None, "Decoding failed"
|
|
|
|
def test_simple_generation(self, model_and_tokenizer):
|
|
"""Test simple text generation"""
|
|
model, tokenizer = model_and_tokenizer
|
|
|
|
prompt = "[verse]\nSinging in the moonlight\n"
|
|
inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
|
|
|
|
outputs = model.generate(
|
|
**inputs,
|
|
max_new_tokens=50,
|
|
temperature=0.7,
|
|
do_sample=True,
|
|
pad_token_id=tokenizer.eos_token_id
|
|
)
|
|
|
|
generated_text = tokenizer.decode(outputs[0], skip_special_tokens=True)
|
|
|
|
assert generated_text is not None, "Generation produced no output"
|
|
assert len(generated_text) > len(prompt), "Generated text should be longer than prompt"
|
|
print(f"Generated: {generated_text[:100]}...")
|
|
|
|
def test_genre_conditioning(self, model_and_tokenizer):
|
|
"""Test generation with genre tags"""
|
|
model, tokenizer = model_and_tokenizer
|
|
|
|
# Test with genre tags
|
|
genre = "rock powerful male guitar"
|
|
lyrics = "[verse]\nBreaking through the walls\n"
|
|
prompt = f"{genre}\n\n{lyrics}"
|
|
|
|
inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
|
|
|
|
outputs = model.generate(
|
|
**inputs,
|
|
max_new_tokens=30,
|
|
temperature=0.8,
|
|
do_sample=True,
|
|
pad_token_id=tokenizer.eos_token_id
|
|
)
|
|
|
|
generated_text = tokenizer.decode(outputs[0], skip_special_tokens=True)
|
|
assert generated_text is not None, "Genre-conditioned generation failed"
|
|
|
|
def test_batch_generation(self, model_and_tokenizer):
|
|
"""Test batch generation"""
|
|
model, tokenizer = model_and_tokenizer
|
|
|
|
prompts = [
|
|
"[verse]\nFirst song lyrics\n",
|
|
"[chorus]\nSecond song lyrics\n"
|
|
]
|
|
|
|
inputs = tokenizer(prompts, return_tensors="pt", padding=True).to(model.device)
|
|
|
|
outputs = model.generate(
|
|
**inputs,
|
|
max_new_tokens=20,
|
|
temperature=0.7,
|
|
do_sample=True,
|
|
pad_token_id=tokenizer.eos_token_id
|
|
)
|
|
|
|
assert outputs.shape[0] == len(prompts), "Batch size mismatch"
|
|
|
|
for i, output in enumerate(outputs):
|
|
text = tokenizer.decode(output, skip_special_tokens=True)
|
|
assert text is not None, f"Batch generation failed for prompt {i}"
|
|
|
|
@pytest.mark.skipif(not torch.cuda.is_available(), reason="CUDA not available")
|
|
def test_cuda_inference(self, model_and_tokenizer):
|
|
"""Test inference on CUDA device"""
|
|
model, tokenizer = model_and_tokenizer
|
|
|
|
assert model.device.type == "cuda", "Model should be on CUDA"
|
|
|
|
prompt = "[verse]\nTest CUDA inference\n"
|
|
inputs = tokenizer(prompt, return_tensors="pt").to("cuda")
|
|
|
|
outputs = model.generate(
|
|
**inputs,
|
|
max_new_tokens=20,
|
|
temperature=0.7,
|
|
pad_token_id=tokenizer.eos_token_id
|
|
)
|
|
|
|
assert outputs.device.type == "cuda", "Outputs should be on CUDA"
|
|
|
|
def test_memory_efficiency(self, model_and_tokenizer):
|
|
"""Test that model fits in available memory"""
|
|
model, _ = model_and_tokenizer
|
|
|
|
if torch.cuda.is_available():
|
|
memory_allocated = torch.cuda.memory_allocated() / 1024**3 # GB
|
|
memory_reserved = torch.cuda.memory_reserved() / 1024**3 # GB
|
|
|
|
print(f"GPU Memory Allocated: {memory_allocated:.2f} GB")
|
|
print(f"GPU Memory Reserved: {memory_reserved:.2f} GB")
|
|
|
|
# Model should fit in 24GB for fp16
|
|
assert memory_allocated < 24, "Model uses too much memory"
|
|
|
|
|
|
@pytest.mark.slow
|
|
class TestZenMusicianEndToEnd:
|
|
"""End-to-end tests for full song generation pipeline"""
|
|
|
|
def test_full_song_generation(self):
|
|
"""Test full song generation with multiple sections"""
|
|
pytest.skip("Requires full inference pipeline setup")
|
|
|
|
def test_dual_track_icl(self):
|
|
"""Test dual-track in-context learning"""
|
|
pytest.skip("Requires audio processing setup")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
pytest.main([__file__, "-v", "--tb=short"]) |