style: 使用ruff 工具优化代码风格
This commit is contained in:
+21
-18
@@ -17,14 +17,14 @@ class RandomDataset(Dataset):
|
||||
self.length = length or int(np.random.randint(100, 200))
|
||||
self.max_length = max_length
|
||||
self.vocab_size = vocab_size
|
||||
|
||||
|
||||
def __len__(self):
|
||||
return self.length
|
||||
|
||||
|
||||
def __getitem__(self, idx):
|
||||
return {
|
||||
"input_ids": torch.randint(0, self.vocab_size, (self.max_length,)),
|
||||
"target_ids": torch.randint(0, self.vocab_size, (self.max_length,))
|
||||
"target_ids": torch.randint(0, self.vocab_size, (self.max_length,)),
|
||||
}
|
||||
|
||||
|
||||
@@ -33,10 +33,10 @@ class MultiTurnDataset(Dataset):
|
||||
self.length = length or int(np.random.randint(100, 200))
|
||||
self.max_length = max_length
|
||||
self.vocab_size = vocab_size
|
||||
|
||||
|
||||
def __len__(self):
|
||||
return self.length
|
||||
|
||||
|
||||
def __getitem__(self, idx):
|
||||
input_ids = torch.randint(0, self.vocab_size, (self.max_length,))
|
||||
target_ids = torch.randint(0, self.vocab_size, (self.max_length,))
|
||||
@@ -54,18 +54,18 @@ class EarlyStoppingDataset(Dataset):
|
||||
self.length = length
|
||||
self.stop_after = stop_after
|
||||
self.count = 0
|
||||
|
||||
|
||||
def __len__(self):
|
||||
return self.length
|
||||
|
||||
|
||||
def __getitem__(self, idx):
|
||||
self.count += 1
|
||||
if self.count == self.stop_after:
|
||||
raise RuntimeError("Simulated early stopping")
|
||||
|
||||
|
||||
return {
|
||||
"input_ids": torch.randint(0, 1000, (64,)),
|
||||
"target_ids": torch.randint(0, 1000, (64,))
|
||||
"target_ids": torch.randint(0, 1000, (64,)),
|
||||
}
|
||||
|
||||
|
||||
@@ -74,10 +74,10 @@ def base_test_env(request: pytest.FixtureRequest):
|
||||
func_name = request.function.__name__
|
||||
test_dir = tempfile.mkdtemp(prefix=f"{func_name}_")
|
||||
config_path = os.path.join(test_dir, "config.json")
|
||||
|
||||
|
||||
n_dim_choices = [8, 16, 32]
|
||||
n_head_choices = [2, 4]
|
||||
|
||||
|
||||
dim = int(np.random.choice(n_dim_choices))
|
||||
n_heads = int(np.random.choice(n_head_choices))
|
||||
n_kv_heads = n_heads // 2
|
||||
@@ -91,16 +91,16 @@ def base_test_env(request: pytest.FixtureRequest):
|
||||
"dim_ffn": dim_ffn,
|
||||
"max_len": 1024,
|
||||
"n_layers": 4,
|
||||
"norm_eps": 1e-5
|
||||
"norm_eps": 1e-5,
|
||||
}
|
||||
|
||||
with open(config_path, 'w') as f:
|
||||
|
||||
with open(config_path, "w") as f:
|
||||
json.dump(config, f)
|
||||
device = "cuda" if torch.cuda.is_available() else "cpu"
|
||||
transformer_config = ModelConfig().load(config_path)
|
||||
model = Transformer(transformer_config).to(device=device)
|
||||
tokenizer = BpeTokenizer()
|
||||
|
||||
|
||||
yield {
|
||||
"device": device,
|
||||
"test_dir": str(test_dir),
|
||||
@@ -109,20 +109,23 @@ def base_test_env(request: pytest.FixtureRequest):
|
||||
"model": model,
|
||||
"tokenizer": tokenizer,
|
||||
}
|
||||
|
||||
|
||||
shutil.rmtree(test_dir)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def random_dataset():
|
||||
dataset = RandomDataset()
|
||||
yield dataset
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def multi_turn_dataset():
|
||||
dataset = MultiTurnDataset()
|
||||
yield dataset
|
||||
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def early_stopping_dataset():
|
||||
dataset = EarlyStoppingDataset()
|
||||
yield dataset
|
||||
yield dataset
|
||||
|
||||
@@ -7,6 +7,7 @@ from torch.optim.lr_scheduler import CosineAnnealingLR
|
||||
from khaosz.data.serialization import Checkpoint
|
||||
from khaosz.parallel.setup import get_rank, spawn_parallel_fn
|
||||
|
||||
|
||||
def test_single_process():
|
||||
model = torch.nn.Linear(10, 5)
|
||||
optimizer = AdamW(model.parameters(), lr=1e-3)
|
||||
@@ -14,34 +15,31 @@ def test_single_process():
|
||||
|
||||
for epoch in range(3):
|
||||
for iteration in range(10):
|
||||
|
||||
x = torch.randn(32, 10)
|
||||
y = torch.randn(32, 5)
|
||||
loss = model(x).mean()
|
||||
loss.backward()
|
||||
optimizer.step()
|
||||
optimizer.zero_grad()
|
||||
|
||||
|
||||
scheduler.step()
|
||||
|
||||
checkpoint = Checkpoint(
|
||||
state_dict=model.state_dict(),
|
||||
epoch=3,
|
||||
iteration=30
|
||||
)
|
||||
|
||||
|
||||
checkpoint = Checkpoint(state_dict=model.state_dict(), epoch=3, iteration=30)
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
checkpoint.save(tmpdir)
|
||||
|
||||
|
||||
loaded_checkpoint = Checkpoint.load(tmpdir)
|
||||
|
||||
|
||||
assert loaded_checkpoint.epoch == 3
|
||||
assert loaded_checkpoint.iteration == 30
|
||||
|
||||
|
||||
def simple_training():
|
||||
model = torch.nn.Linear(10, 5)
|
||||
optimizer = AdamW(model.parameters(), lr=1e-3)
|
||||
scheduler = CosineAnnealingLR(optimizer, T_max=10)
|
||||
|
||||
|
||||
for epoch in range(2):
|
||||
for iteration in range(5):
|
||||
x = torch.randn(16, 10)
|
||||
@@ -57,28 +55,23 @@ def simple_training():
|
||||
epoch=2,
|
||||
iteration=10,
|
||||
)
|
||||
|
||||
|
||||
rank = get_rank()
|
||||
|
||||
|
||||
if rank == 0:
|
||||
shared_dir = tempfile.mkdtemp()
|
||||
checkpoint.save(shared_dir)
|
||||
else:
|
||||
shared_dir = None
|
||||
|
||||
|
||||
if dist.is_initialized():
|
||||
dir_list = [shared_dir]
|
||||
dist.broadcast_object_list(dir_list, src=0)
|
||||
shared_dir = dir_list[0]
|
||||
|
||||
|
||||
|
||||
loaded = Checkpoint.load(shared_dir)
|
||||
assert loaded.epoch == 2
|
||||
|
||||
|
||||
def test_multi_process():
|
||||
spawn_parallel_fn(
|
||||
simple_training,
|
||||
world_size=2,
|
||||
backend="gloo"
|
||||
)
|
||||
spawn_parallel_fn(simple_training, world_size=2, backend="gloo")
|
||||
|
||||
+44
-45
@@ -5,30 +5,32 @@ from khaosz.data.serialization import save_h5
|
||||
from khaosz.data.dataset import *
|
||||
|
||||
|
||||
|
||||
def test_dataset_loader_random_paths(base_test_env):
|
||||
"""Test dataset loader with multiple random paths"""
|
||||
test_dir = base_test_env["test_dir"]
|
||||
|
||||
|
||||
# Create multiple mmap dataset directories with random data
|
||||
num_files = np.random.randint(2, 5)
|
||||
|
||||
|
||||
for i in range(num_files):
|
||||
seq_length = np.random.randint(200, 400)
|
||||
dummy_data = {
|
||||
"sequence": [torch.randint(0, 1000, (seq_length,), dtype=torch.int64) for _ in range(10)],
|
||||
"sequence": [
|
||||
torch.randint(0, 1000, (seq_length,), dtype=torch.int64)
|
||||
for _ in range(10)
|
||||
],
|
||||
}
|
||||
save_h5(test_dir, f"data_{i}", dummy_data)
|
||||
|
||||
|
||||
# Test loading with multiple paths
|
||||
loaded_dataset = DatasetLoader.load(
|
||||
train_type="seq",
|
||||
load_path=test_dir,
|
||||
window_size=64,
|
||||
train_type="seq",
|
||||
load_path=test_dir,
|
||||
window_size=64,
|
||||
)
|
||||
assert loaded_dataset is not None
|
||||
assert len(loaded_dataset) > 0
|
||||
|
||||
|
||||
# Test that we can get items without errors
|
||||
for i in range(len(loaded_dataset)):
|
||||
item = loaded_dataset[i]
|
||||
@@ -41,30 +43,30 @@ def test_dataset_loader_random_paths(base_test_env):
|
||||
def test_dpo_strategy_with_random_data(base_test_env):
|
||||
"""Test DPO strategy with randomized preference data"""
|
||||
test_dir = base_test_env["test_dir"]
|
||||
|
||||
|
||||
# Create DPO-style data with memory mapping format
|
||||
seq_length = np.random.randint(100, 200)
|
||||
|
||||
|
||||
dummy_data = {
|
||||
"chosen": [torch.randint(0, 1000, (seq_length,), dtype=torch.int64)],
|
||||
"rejected": [torch.randint(0, 1000, (seq_length,), dtype=torch.int64)],
|
||||
"chosen_mask": [torch.ones(seq_length, dtype=torch.bool)],
|
||||
"rejected_mask": [torch.ones(seq_length, dtype=torch.bool)]
|
||||
"rejected_mask": [torch.ones(seq_length, dtype=torch.bool)],
|
||||
}
|
||||
|
||||
|
||||
save_h5(test_dir, "dpo_data", dummy_data)
|
||||
|
||||
|
||||
# Load DPO dataset
|
||||
dpo_dataset = DatasetLoader.load(
|
||||
train_type="dpo",
|
||||
load_path=test_dir,
|
||||
window_size=64,
|
||||
train_type="dpo",
|
||||
load_path=test_dir,
|
||||
window_size=64,
|
||||
)
|
||||
|
||||
|
||||
assert dpo_dataset is not None
|
||||
assert hasattr(dpo_dataset, 'fetcher')
|
||||
assert hasattr(dpo_dataset, "fetcher")
|
||||
assert len(dpo_dataset) > 0
|
||||
|
||||
|
||||
# Test that we can get DPO items without errors
|
||||
for i in range(min(3, len(dpo_dataset))):
|
||||
item = dpo_dataset[i]
|
||||
@@ -79,28 +81,28 @@ def test_dpo_strategy_with_random_data(base_test_env):
|
||||
def test_sft_dataset_with_random_data(base_test_env):
|
||||
"""Test SFT dataset with random data"""
|
||||
test_dir = base_test_env["test_dir"]
|
||||
|
||||
|
||||
# Create SFT-style data with memory mapping format
|
||||
seq_length = np.random.randint(100, 200)
|
||||
|
||||
|
||||
dummy_data = {
|
||||
"sequence": [torch.randint(0, 1000, (seq_length,), dtype=torch.int64)],
|
||||
"loss_mask": [torch.ones(seq_length, dtype=torch.bool)]
|
||||
"loss_mask": [torch.ones(seq_length, dtype=torch.bool)],
|
||||
}
|
||||
|
||||
|
||||
save_h5(test_dir, "sft_data", dummy_data)
|
||||
|
||||
|
||||
# Load SFT dataset
|
||||
sft_dataset = DatasetLoader.load(
|
||||
train_type="sft",
|
||||
load_path=test_dir,
|
||||
window_size=64,
|
||||
train_type="sft",
|
||||
load_path=test_dir,
|
||||
window_size=64,
|
||||
)
|
||||
|
||||
|
||||
assert sft_dataset is not None
|
||||
assert hasattr(sft_dataset, 'fetcher')
|
||||
assert hasattr(sft_dataset, "fetcher")
|
||||
assert len(sft_dataset) > 0
|
||||
|
||||
|
||||
# Test that we can get SFT items without errors
|
||||
for i in range(min(3, len(sft_dataset))):
|
||||
item = sft_dataset[i]
|
||||
@@ -114,33 +116,30 @@ def test_sft_dataset_with_random_data(base_test_env):
|
||||
def test_dataset_with_custom_stride(base_test_env):
|
||||
"""Test dataset with custom stride parameter"""
|
||||
test_dir = base_test_env["test_dir"]
|
||||
|
||||
|
||||
# Create test data
|
||||
seq_length = 200
|
||||
dummy_data = {
|
||||
"sequence": [torch.randint(0, 1000, (seq_length,), dtype=torch.int64)],
|
||||
}
|
||||
|
||||
save_h5(test_dir,"stride_test_data", dummy_data)
|
||||
|
||||
|
||||
save_h5(test_dir, "stride_test_data", dummy_data)
|
||||
|
||||
# Test with custom stride
|
||||
custom_stride = 32
|
||||
dataset = DatasetLoader.load(
|
||||
train_type="seq",
|
||||
load_path=test_dir,
|
||||
window_size=64,
|
||||
stride=custom_stride
|
||||
train_type="seq", load_path=test_dir, window_size=64, stride=custom_stride
|
||||
)
|
||||
|
||||
|
||||
assert dataset is not None
|
||||
assert len(dataset) > 0
|
||||
|
||||
|
||||
# With stride 32 and window 64 on 200 length data, we should get more samples
|
||||
# than with default stride (which equals window size)
|
||||
default_stride_dataset = DatasetLoader.load(
|
||||
train_type="seq",
|
||||
load_path=test_dir,
|
||||
window_size=64,
|
||||
train_type="seq",
|
||||
load_path=test_dir,
|
||||
window_size=64,
|
||||
)
|
||||
|
||||
|
||||
assert len(dataset) > len(default_stride_dataset)
|
||||
|
||||
+14
-12
@@ -1,30 +1,32 @@
|
||||
from khaosz.trainer import *
|
||||
from khaosz.data import *
|
||||
|
||||
|
||||
def test_random_sampler_consistency(random_dataset):
|
||||
"""Test RandomSampler produces consistent results with same seed"""
|
||||
dataset = random_dataset
|
||||
|
||||
|
||||
# Create two samplers with same seed
|
||||
sampler1 = ResumableDistributedSampler(dataset, seed=42)
|
||||
sampler2 = ResumableDistributedSampler(dataset, seed=42)
|
||||
|
||||
|
||||
indices1 = list(iter(sampler1))
|
||||
indices2 = list(iter(sampler2))
|
||||
|
||||
|
||||
assert indices1 == indices2
|
||||
|
||||
|
||||
def test_random_sampler_different_seeds(random_dataset):
|
||||
"""Test RandomSampler produces different results with different seeds"""
|
||||
dataset = random_dataset
|
||||
|
||||
|
||||
# Create two samplers with different seeds
|
||||
sampler1 = ResumableDistributedSampler(dataset, seed=42)
|
||||
sampler2 = ResumableDistributedSampler(dataset, seed=123)
|
||||
|
||||
|
||||
indices1 = list(iter(sampler1))
|
||||
indices2 = list(iter(sampler2))
|
||||
|
||||
|
||||
# Very high probability they should be different
|
||||
assert indices1 != indices2
|
||||
|
||||
@@ -33,20 +35,20 @@ def test_sampler_across_epochs(random_dataset):
|
||||
"""Test sampler behavior across multiple epochs"""
|
||||
dataset = random_dataset
|
||||
n = len(dataset)
|
||||
|
||||
|
||||
sampler = ResumableDistributedSampler(dataset, seed=42)
|
||||
|
||||
|
||||
# Get indices for first epoch
|
||||
epoch1_indices = list(iter(sampler))
|
||||
assert len(epoch1_indices) == n
|
||||
|
||||
|
||||
# Get indices for second epoch
|
||||
epoch2_indices = list(iter(sampler))
|
||||
assert len(epoch2_indices) == n
|
||||
|
||||
|
||||
# Check that epochs have different order (should be random)
|
||||
assert epoch1_indices != epoch2_indices
|
||||
|
||||
|
||||
# Check that all indices are present in each epoch
|
||||
assert set(epoch1_indices) == set(range(n))
|
||||
assert set(epoch2_indices) == set(range(n))
|
||||
assert set(epoch2_indices) == set(range(n))
|
||||
|
||||
+32
-23
@@ -12,6 +12,7 @@ from khaosz.data import *
|
||||
from khaosz.inference.generator import EmbeddingEncoderCore, GeneratorCore
|
||||
from tokenizers import pre_tokenizers
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def test_env(request: pytest.FixtureRequest):
|
||||
func_name = request.function.__name__
|
||||
@@ -19,7 +20,7 @@ def test_env(request: pytest.FixtureRequest):
|
||||
config_path = os.path.join(test_dir, "config.json")
|
||||
tokenizer_path = os.path.join(test_dir, "tokenizer.json")
|
||||
model_path = os.path.join(test_dir, "model.safetensors")
|
||||
|
||||
|
||||
config = {
|
||||
"vocab_size": 1000,
|
||||
"dim": 128,
|
||||
@@ -28,20 +29,20 @@ def test_env(request: pytest.FixtureRequest):
|
||||
"dim_ffn": 256,
|
||||
"max_len": 64,
|
||||
"n_layers": 2,
|
||||
"norm_eps": 1e-5
|
||||
"norm_eps": 1e-5,
|
||||
}
|
||||
with open(config_path, 'w') as f:
|
||||
with open(config_path, "w") as f:
|
||||
json.dump(config, f)
|
||||
|
||||
|
||||
tokenizer = BpeTokenizer()
|
||||
sp_token_iter = iter(pre_tokenizers.ByteLevel.alphabet())
|
||||
tokenizer.train_from_iterator(sp_token_iter, config["vocab_size"], 1)
|
||||
tokenizer.save(tokenizer_path)
|
||||
|
||||
|
||||
transformer_config = ModelConfig().load(config_path)
|
||||
model = Transformer(transformer_config)
|
||||
st.save_file(model.state_dict(), model_path)
|
||||
|
||||
|
||||
yield {
|
||||
"test_dir": test_dir,
|
||||
"model": model,
|
||||
@@ -51,47 +52,55 @@ def test_env(request: pytest.FixtureRequest):
|
||||
|
||||
shutil.rmtree(test_dir)
|
||||
|
||||
|
||||
def test_model_parameter(test_env):
|
||||
save_dir = os.path.join(test_env["test_dir"], "save")
|
||||
model_param = ModelParameter(test_env["model"],test_env["tokenizer"] , test_env["transformer_config"])
|
||||
model_param = ModelParameter(
|
||||
test_env["model"], test_env["tokenizer"], test_env["transformer_config"]
|
||||
)
|
||||
ModelParameter.save(model_param, save_dir)
|
||||
|
||||
|
||||
assert os.path.exists(os.path.join(save_dir, "model.safetensors"))
|
||||
assert os.path.exists(os.path.join(save_dir, "tokenizer.json"))
|
||||
assert os.path.exists(os.path.join(save_dir, "config.json"))
|
||||
|
||||
|
||||
# transformer
|
||||
def test_transformer(test_env):
|
||||
model = test_env["model"]
|
||||
input_ids = torch.randint(0, test_env["transformer_config"].vocab_size,
|
||||
(4, test_env["transformer_config"].max_len))
|
||||
input_ids = torch.randint(
|
||||
0,
|
||||
test_env["transformer_config"].vocab_size,
|
||||
(4, test_env["transformer_config"].max_len),
|
||||
)
|
||||
output_logits = model(input_ids)["logits"]
|
||||
target_shape = (4, test_env["transformer_config"].max_len, test_env["transformer_config"].vocab_size)
|
||||
target_shape = (
|
||||
4,
|
||||
test_env["transformer_config"].max_len,
|
||||
test_env["transformer_config"].vocab_size,
|
||||
)
|
||||
assert output_logits.shape == target_shape
|
||||
|
||||
|
||||
|
||||
# generator
|
||||
def test_embedding_encoder_core(test_env):
|
||||
parameter = ModelParameter(
|
||||
test_env["model"],
|
||||
test_env["tokenizer"],
|
||||
test_env["transformer_config"]
|
||||
test_env["model"], test_env["tokenizer"], test_env["transformer_config"]
|
||||
)
|
||||
encoder = EmbeddingEncoderCore(parameter)
|
||||
|
||||
|
||||
single_emb = encoder.encode("测试文本")
|
||||
assert isinstance(single_emb, torch.Tensor)
|
||||
assert single_emb.shape[-1] == test_env["transformer_config"].dim
|
||||
|
||||
|
||||
batch_emb = encoder.encode(["测试1", "测试2"])
|
||||
assert isinstance(batch_emb, list)
|
||||
assert len(batch_emb) == 2
|
||||
|
||||
|
||||
|
||||
def test_generator_core(test_env):
|
||||
parameter = ModelParameter(
|
||||
test_env["model"],
|
||||
test_env["tokenizer"],
|
||||
test_env["transformer_config"]
|
||||
test_env["model"], test_env["tokenizer"], test_env["transformer_config"]
|
||||
)
|
||||
generator = GeneratorCore(parameter)
|
||||
input_ids = torch.randint(0, test_env["transformer_config"].vocab_size, (4, 10))
|
||||
@@ -102,8 +111,8 @@ def test_generator_core(test_env):
|
||||
top_p=0.95,
|
||||
attn_mask=None,
|
||||
kv_caches=None,
|
||||
start_pos=0
|
||||
start_pos=0,
|
||||
)
|
||||
|
||||
|
||||
assert next_token_id.shape == (4, 1)
|
||||
assert cache_increase == 10
|
||||
|
||||
@@ -13,7 +13,7 @@ def transformer_test_env():
|
||||
"""创建Transformer测试专用环境"""
|
||||
test_dir = tempfile.mkdtemp(prefix="transformer_test_")
|
||||
config_path = os.path.join(test_dir, "config.json")
|
||||
|
||||
|
||||
config = {
|
||||
"vocab_size": 1000,
|
||||
"dim": 128,
|
||||
@@ -22,18 +22,14 @@ def transformer_test_env():
|
||||
"dim_ffn": 256,
|
||||
"max_len": 64,
|
||||
"n_layers": 2,
|
||||
"norm_eps": 1e-5
|
||||
"norm_eps": 1e-5,
|
||||
}
|
||||
|
||||
with open(config_path, 'w') as f:
|
||||
|
||||
with open(config_path, "w") as f:
|
||||
json.dump(config, f)
|
||||
|
||||
yield {
|
||||
"test_dir": test_dir,
|
||||
"config_path": config_path,
|
||||
"config": config
|
||||
}
|
||||
|
||||
|
||||
yield {"test_dir": test_dir, "config_path": config_path, "config": config}
|
||||
|
||||
if os.path.exists(test_dir):
|
||||
try:
|
||||
for file in os.listdir(test_dir):
|
||||
@@ -46,74 +42,75 @@ def transformer_test_env():
|
||||
def test_tie_weight_init(transformer_test_env):
|
||||
config_path = transformer_test_env["config_path"]
|
||||
config_data = transformer_test_env["config"].copy()
|
||||
|
||||
|
||||
# case 1: tie weight
|
||||
config_data["tie_weight"] = True
|
||||
|
||||
with open(config_path, 'w') as f:
|
||||
|
||||
with open(config_path, "w") as f:
|
||||
json.dump(config_data, f)
|
||||
|
||||
|
||||
config = ModelConfig().load(config_path)
|
||||
model = Transformer(config)
|
||||
|
||||
|
||||
assert torch.equal(model.lm_head.weight, model.embed_tokens.weight)
|
||||
assert model.lm_head.weight.data_ptr() == model.embed_tokens.weight.data_ptr()
|
||||
|
||||
|
||||
original_weight = model.embed_tokens.weight.clone()
|
||||
model.embed_tokens.weight.data[0, 0] = 100.0
|
||||
|
||||
|
||||
assert torch.equal(model.lm_head.weight, model.embed_tokens.weight)
|
||||
assert not torch.equal(model.lm_head.weight, original_weight)
|
||||
|
||||
|
||||
# case 2: not tie weight
|
||||
config_data["tie_weight"] = False
|
||||
|
||||
with open(config_path, 'w') as f:
|
||||
|
||||
with open(config_path, "w") as f:
|
||||
json.dump(config_data, f)
|
||||
|
||||
|
||||
config = ModelConfig().load(config_path)
|
||||
model = Transformer(config)
|
||||
|
||||
|
||||
assert not torch.equal(model.lm_head.weight, model.embed_tokens.weight)
|
||||
assert model.lm_head.weight.data_ptr() != model.embed_tokens.weight.data_ptr()
|
||||
|
||||
original_weight = model.embed_tokens.weight.clone()
|
||||
model.embed_tokens.weight.data[0, 0] = 100.0
|
||||
|
||||
|
||||
assert not torch.equal(model.lm_head.weight, model.embed_tokens.weight)
|
||||
assert not torch.equal(model.lm_head.weight, original_weight)
|
||||
|
||||
|
||||
def test_model_save_load_with_tie_weight(transformer_test_env):
|
||||
test_dir = transformer_test_env["test_dir"]
|
||||
model_path = os.path.join(test_dir, "model.safetensors")
|
||||
|
||||
|
||||
config_data = transformer_test_env["config"].copy()
|
||||
|
||||
|
||||
# case 1: tie weight
|
||||
config_data["tie_weight"] = True
|
||||
config_path = os.path.join(test_dir, "config.json")
|
||||
|
||||
with open(config_path, 'w') as f:
|
||||
|
||||
with open(config_path, "w") as f:
|
||||
json.dump(config_data, f)
|
||||
|
||||
|
||||
config = ModelConfig().load(config_path)
|
||||
original_model = Transformer(config)
|
||||
|
||||
|
||||
st.save_file(original_model.state_dict(), model_path)
|
||||
|
||||
loaded_config = ModelConfig().load(config_path)
|
||||
model = Transformer(loaded_config)
|
||||
model.load_state_dict(st.load_file(model_path))
|
||||
|
||||
|
||||
assert torch.equal(model.lm_head.weight, model.embed_tokens.weight)
|
||||
assert model.lm_head.weight.data_ptr() == model.embed_tokens.weight.data_ptr()
|
||||
assert "lm_head.weight" not in model.state_dict()
|
||||
|
||||
# case 2: not tie weight (form tie-weight state dict load)
|
||||
config_data["tie_weight"] = False
|
||||
with open(config_path, 'w') as f:
|
||||
with open(config_path, "w") as f:
|
||||
json.dump(config_data, f)
|
||||
|
||||
|
||||
loaded_config = ModelConfig().load(config_path)
|
||||
model = Transformer(loaded_config)
|
||||
model.load_state_dict(st.load_file(model_path))
|
||||
@@ -121,4 +118,3 @@ def test_model_save_load_with_tie_weight(transformer_test_env):
|
||||
assert torch.equal(model.lm_head.weight, model.embed_tokens.weight)
|
||||
assert model.lm_head.weight.data_ptr() != model.embed_tokens.weight.data_ptr()
|
||||
assert "lm_head.weight" in model.state_dict()
|
||||
|
||||
+8
-15
@@ -1,16 +1,14 @@
|
||||
import torch
|
||||
import torch.distributed as dist
|
||||
|
||||
from khaosz.parallel import (
|
||||
get_rank,
|
||||
only_on_rank,
|
||||
spawn_parallel_fn
|
||||
)
|
||||
from khaosz.parallel import get_rank, only_on_rank, spawn_parallel_fn
|
||||
|
||||
|
||||
@only_on_rank(0)
|
||||
def _test_only_on_rank_helper():
|
||||
return True
|
||||
|
||||
|
||||
def only_on_rank():
|
||||
result = _test_only_on_rank_helper()
|
||||
if get_rank() == 0:
|
||||
@@ -18,22 +16,17 @@ def only_on_rank():
|
||||
else:
|
||||
assert result is None
|
||||
|
||||
|
||||
def all_reduce():
|
||||
x = torch.tensor([get_rank()], dtype=torch.int)
|
||||
dist.all_reduce(x, op=dist.ReduceOp.SUM)
|
||||
expected_sum = sum(range(dist.get_world_size()))
|
||||
assert x.item() == expected_sum
|
||||
|
||||
|
||||
def test_spawn_only_on_rank():
|
||||
spawn_parallel_fn(
|
||||
only_on_rank,
|
||||
world_size=2,
|
||||
backend="gloo"
|
||||
)
|
||||
spawn_parallel_fn(only_on_rank, world_size=2, backend="gloo")
|
||||
|
||||
|
||||
def test_spawn_all_reduce():
|
||||
spawn_parallel_fn(
|
||||
all_reduce,
|
||||
world_size=2,
|
||||
backend="gloo"
|
||||
)
|
||||
spawn_parallel_fn(all_reduce, world_size=2, backend="gloo")
|
||||
|
||||
@@ -3,57 +3,48 @@ import torch
|
||||
from khaosz.config import *
|
||||
from khaosz.trainer import *
|
||||
|
||||
|
||||
def test_callback_integration(base_test_env, random_dataset):
|
||||
"""Test that all callbacks are properly integrated"""
|
||||
schedule_config = CosineScheduleConfig(
|
||||
warmup_steps=10,
|
||||
total_steps=20
|
||||
)
|
||||
|
||||
schedule_config = CosineScheduleConfig(warmup_steps=10, total_steps=20)
|
||||
|
||||
optimizer_fn = lambda model: torch.optim.AdamW(model.parameters())
|
||||
scheduler_fn = lambda optim: SchedulerFactory.load(optim, schedule_config)
|
||||
|
||||
|
||||
train_config = TrainConfig(
|
||||
model=base_test_env["model"],
|
||||
strategy='seq',
|
||||
strategy="seq",
|
||||
dataset=random_dataset,
|
||||
optimizer_fn=optimizer_fn,
|
||||
scheduler_fn=scheduler_fn,
|
||||
checkpoint_dir=base_test_env["test_dir"],
|
||||
ckpt_dir=base_test_env["test_dir"],
|
||||
n_epoch=1,
|
||||
batch_size=2,
|
||||
checkpoint_interval=3,
|
||||
ckpt_interval=3,
|
||||
accumulation_steps=1,
|
||||
max_grad_norm=1.0,
|
||||
random_seed=42,
|
||||
device_type=base_test_env["device"]
|
||||
device_type=base_test_env["device"],
|
||||
)
|
||||
|
||||
|
||||
|
||||
# Create custom callbacks to track calls
|
||||
callback_calls = []
|
||||
|
||||
|
||||
class TrackingCallback(TrainCallback):
|
||||
def on_train_begin(self, context):
|
||||
callback_calls.append('on_train_begin')
|
||||
|
||||
callback_calls.append("on_train_begin")
|
||||
|
||||
def on_batch_end(self, context):
|
||||
callback_calls.append('on_batch_end')
|
||||
|
||||
callback_calls.append("on_batch_end")
|
||||
|
||||
def on_epoch_end(self, context):
|
||||
callback_calls.append('on_epoch_end')
|
||||
|
||||
callback_calls.append("on_epoch_end")
|
||||
|
||||
trainer = Trainer(train_config, callbacks=[TrackingCallback()])
|
||||
|
||||
|
||||
trainer = Trainer(
|
||||
train_config,
|
||||
callbacks=[TrackingCallback()]
|
||||
)
|
||||
|
||||
trainer.train()
|
||||
|
||||
|
||||
# Verify callbacks were called
|
||||
assert 'on_train_begin' in callback_calls
|
||||
assert 'on_batch_end' in callback_calls
|
||||
assert 'on_epoch_end' in callback_calls
|
||||
assert "on_train_begin" in callback_calls
|
||||
assert "on_batch_end" in callback_calls
|
||||
assert "on_epoch_end" in callback_calls
|
||||
|
||||
@@ -5,31 +5,32 @@ from khaosz.config import *
|
||||
from khaosz.trainer import *
|
||||
from khaosz.data.serialization import Checkpoint
|
||||
|
||||
|
||||
def test_early_stopping_simulation(base_test_env, early_stopping_dataset):
|
||||
"""Simulate early stopping behavior"""
|
||||
|
||||
|
||||
schedule_config = CosineScheduleConfig(warmup_steps=10, total_steps=20)
|
||||
|
||||
|
||||
optimizer_fn = lambda model: torch.optim.AdamW(model.parameters())
|
||||
scheduler_fn = lambda optim: SchedulerFactory.load(optim, schedule_config)
|
||||
|
||||
|
||||
train_config = TrainConfig(
|
||||
strategy="seq",
|
||||
optimizer_fn=optimizer_fn,
|
||||
scheduler_fn=scheduler_fn,
|
||||
model=base_test_env["model"],
|
||||
dataset=early_stopping_dataset,
|
||||
checkpoint_dir=base_test_env["test_dir"],
|
||||
ckpt_dir=base_test_env["test_dir"],
|
||||
n_epoch=2,
|
||||
batch_size=2,
|
||||
checkpoint_interval=1,
|
||||
ckpt_interval=1,
|
||||
accumulation_steps=2,
|
||||
random_seed=np.random.randint(1e4),
|
||||
device_type=base_test_env["device"]
|
||||
device_type=base_test_env["device"],
|
||||
)
|
||||
|
||||
trainer = Trainer(train_config)
|
||||
|
||||
|
||||
# Should handle early stopping gracefully
|
||||
checkpoint = None
|
||||
try:
|
||||
@@ -37,11 +38,11 @@ def test_early_stopping_simulation(base_test_env, early_stopping_dataset):
|
||||
except Exception:
|
||||
# Handle any exceptions
|
||||
pass
|
||||
|
||||
|
||||
load_dir = os.path.join(base_test_env["test_dir"], "epoch_0_iter_2")
|
||||
checkpoint = Checkpoint.load(load_dir)
|
||||
trainer.train(checkpoint)
|
||||
|
||||
|
||||
load_dir = os.path.join(base_test_env["test_dir"], "epoch_1_iter_10")
|
||||
checkpoint = Checkpoint.load(load_dir)
|
||||
assert checkpoint.iteration == 10
|
||||
assert checkpoint.iteration == 10
|
||||
|
||||
@@ -9,39 +9,41 @@ from khaosz.data.dataset import *
|
||||
|
||||
def test_schedule_factory_random_configs():
|
||||
"""Test scheduler factory with random configurations"""
|
||||
|
||||
|
||||
# Create a simple model and optimizer for testing
|
||||
model = torch.nn.Linear(10, 2)
|
||||
optimizer = torch.optim.AdamW(model.parameters(), lr=0.001)
|
||||
|
||||
|
||||
# Test multiple random configurations
|
||||
for _ in range(5): # Test 5 random configurations
|
||||
schedule_configs = [
|
||||
CosineScheduleConfig(
|
||||
warmup_steps=np.random.randint(50, 200),
|
||||
total_steps=np.random.randint(1000, 5000),
|
||||
min_rate=np.random.uniform(0.01, 0.1)
|
||||
min_rate=np.random.uniform(0.01, 0.1),
|
||||
),
|
||||
SGDRScheduleConfig(
|
||||
warmup_steps=np.random.randint(50, 200),
|
||||
cycle_length=np.random.randint(500, 2000),
|
||||
t_mult=np.random.randint(1, 3),
|
||||
min_rate=np.random.uniform(0.01, 0.1)
|
||||
)
|
||||
min_rate=np.random.uniform(0.01, 0.1),
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
for config in schedule_configs:
|
||||
# Validate configuration
|
||||
config.validate()
|
||||
|
||||
|
||||
# Create scheduler using factory
|
||||
scheduler = SchedulerFactory.load(optimizer, config)
|
||||
|
||||
|
||||
# Verify scheduler type
|
||||
if isinstance(config, CosineScheduleConfig):
|
||||
assert isinstance(scheduler, CosineScheduler)
|
||||
assert scheduler.warmup_steps == config.warmup_steps
|
||||
assert scheduler.lr_decay_steps == config.total_steps - config.warmup_steps
|
||||
assert (
|
||||
scheduler.lr_decay_steps == config.total_steps - config.warmup_steps
|
||||
)
|
||||
assert scheduler.min_rate == config.min_rate
|
||||
elif isinstance(config, SGDRScheduleConfig):
|
||||
assert isinstance(scheduler, SGDRScheduler)
|
||||
@@ -49,17 +51,17 @@ def test_schedule_factory_random_configs():
|
||||
assert scheduler.cycle_length == config.cycle_length
|
||||
assert scheduler.t_mult == config.t_mult
|
||||
assert scheduler.min_rate == config.min_rate
|
||||
|
||||
|
||||
# Test scheduler state dict functionality
|
||||
state_dict = scheduler.state_dict()
|
||||
assert 'warmup_steps' in state_dict
|
||||
assert 'min_rate' in state_dict
|
||||
|
||||
assert "warmup_steps" in state_dict
|
||||
assert "min_rate" in state_dict
|
||||
|
||||
# Test scheduler step functionality
|
||||
initial_lr = scheduler.get_last_lr()
|
||||
scheduler.step()
|
||||
new_lr = scheduler.get_last_lr()
|
||||
|
||||
|
||||
# Learning rate should change after step, or if it's the first step,
|
||||
# the epoch counter should increment
|
||||
assert initial_lr != new_lr or scheduler.last_epoch > -1
|
||||
@@ -67,10 +69,10 @@ def test_schedule_factory_random_configs():
|
||||
|
||||
def test_schedule_factory_edge_cases():
|
||||
"""Test scheduler factory with edge cases and boundary conditions"""
|
||||
|
||||
|
||||
model = torch.nn.Linear(10, 2)
|
||||
optimizer = torch.optim.AdamW(model.parameters(), lr=0.001)
|
||||
|
||||
|
||||
# Test edge cases for CosineScheduleConfig
|
||||
edge_cases = [
|
||||
# Minimal warmup and steps
|
||||
@@ -80,12 +82,12 @@ def test_schedule_factory_edge_cases():
|
||||
# Zero min_rate (edge case)
|
||||
CosineScheduleConfig(warmup_steps=100, total_steps=1000, min_rate=0.0),
|
||||
]
|
||||
|
||||
|
||||
for config in edge_cases:
|
||||
config.validate()
|
||||
scheduler = SchedulerFactory.load(optimizer, config)
|
||||
assert scheduler is not None
|
||||
|
||||
|
||||
# Test multiple steps
|
||||
for _ in range(10):
|
||||
scheduler.step()
|
||||
@@ -93,7 +95,7 @@ def test_schedule_factory_edge_cases():
|
||||
|
||||
def test_schedule_factory_invalid_configs():
|
||||
"""Test scheduler factory with invalid configurations"""
|
||||
|
||||
|
||||
# Test invalid configurations that should raise errors
|
||||
invalid_configs = [
|
||||
# Negative warmup steps
|
||||
@@ -104,7 +106,7 @@ def test_schedule_factory_invalid_configs():
|
||||
{"warmup_steps": 100, "total_steps": 1000, "min_rate": -0.1},
|
||||
{"warmup_steps": 100, "total_steps": 1000, "min_rate": 1.1},
|
||||
]
|
||||
|
||||
|
||||
for kwargs in invalid_configs:
|
||||
with pytest.raises(ValueError):
|
||||
config = CosineScheduleConfig(**kwargs)
|
||||
@@ -113,24 +115,24 @@ def test_schedule_factory_invalid_configs():
|
||||
|
||||
def test_schedule_factory_state_persistence():
|
||||
"""Test scheduler state persistence (save/load)"""
|
||||
|
||||
|
||||
model = torch.nn.Linear(10, 2)
|
||||
optimizer = torch.optim.AdamW(model.parameters(), lr=0.001)
|
||||
|
||||
|
||||
config = CosineScheduleConfig(warmup_steps=100, total_steps=1000, min_rate=0.1)
|
||||
scheduler = SchedulerFactory.load(optimizer, config)
|
||||
|
||||
|
||||
# Take a few steps
|
||||
for _ in range(5):
|
||||
scheduler.step()
|
||||
|
||||
|
||||
# Save state
|
||||
state_dict = scheduler.state_dict()
|
||||
|
||||
|
||||
# Create new scheduler and load state
|
||||
new_scheduler = SchedulerFactory.load(optimizer, config)
|
||||
new_scheduler.load_state_dict(state_dict)
|
||||
|
||||
|
||||
# Verify states match
|
||||
assert scheduler.last_epoch == new_scheduler.last_epoch
|
||||
assert scheduler.get_last_lr() == new_scheduler.get_last_lr()
|
||||
assert scheduler.get_last_lr() == new_scheduler.get_last_lr()
|
||||
|
||||
@@ -6,100 +6,94 @@ from khaosz.config import *
|
||||
from khaosz.trainer import *
|
||||
from khaosz.data.dataset import *
|
||||
|
||||
|
||||
def test_different_batch_sizes(base_test_env, random_dataset):
|
||||
"""Test training with different batch sizes"""
|
||||
batch_sizes = [1, 2, 4, 8]
|
||||
|
||||
|
||||
for batch_size in batch_sizes:
|
||||
schedule_config = CosineScheduleConfig(
|
||||
warmup_steps=10,
|
||||
total_steps=20
|
||||
)
|
||||
schedule_config = CosineScheduleConfig(warmup_steps=10, total_steps=20)
|
||||
optimizer_fn = lambda model: torch.optim.AdamW(model.parameters())
|
||||
scheduler_fn = lambda optim: SchedulerFactory.load(optim, schedule_config)
|
||||
|
||||
|
||||
train_config = TrainConfig(
|
||||
strategy="seq",
|
||||
model=base_test_env["model"],
|
||||
dataset=random_dataset,
|
||||
optimizer_fn=optimizer_fn,
|
||||
scheduler_fn=scheduler_fn,
|
||||
checkpoint_dir=base_test_env["test_dir"],
|
||||
ckpt_dir=base_test_env["test_dir"],
|
||||
n_epoch=1,
|
||||
batch_size=batch_size,
|
||||
checkpoint_interval=5,
|
||||
ckpt_interval=5,
|
||||
accumulation_steps=1,
|
||||
max_grad_norm=1.0,
|
||||
random_seed=np.random.randint(1000),
|
||||
device_type=base_test_env["device"]
|
||||
device_type=base_test_env["device"],
|
||||
)
|
||||
|
||||
|
||||
assert train_config.batch_size == batch_size
|
||||
|
||||
|
||||
def test_gradient_accumulation(base_test_env, random_dataset):
|
||||
"""Test training with different gradient accumulation steps"""
|
||||
accumulation_steps_list = [1, 2, 4]
|
||||
|
||||
|
||||
for accumulation_steps in accumulation_steps_list:
|
||||
schedule_config = CosineScheduleConfig(
|
||||
warmup_steps=10,
|
||||
total_steps=20
|
||||
)
|
||||
schedule_config = CosineScheduleConfig(warmup_steps=10, total_steps=20)
|
||||
optimizer_fn = lambda model: torch.optim.AdamW(model.parameters())
|
||||
scheduler_fn = lambda optim: SchedulerFactory.load(optim, schedule_config)
|
||||
|
||||
|
||||
train_config = TrainConfig(
|
||||
strategy="seq",
|
||||
model=base_test_env["model"],
|
||||
optimizer_fn=optimizer_fn,
|
||||
scheduler_fn=scheduler_fn,
|
||||
dataset=random_dataset,
|
||||
checkpoint_dir=base_test_env["test_dir"],
|
||||
ckpt_dir=base_test_env["test_dir"],
|
||||
n_epoch=1,
|
||||
batch_size=2,
|
||||
checkpoint_interval=10,
|
||||
ckpt_interval=10,
|
||||
accumulation_steps=accumulation_steps,
|
||||
max_grad_norm=1.0,
|
||||
random_seed=42,
|
||||
device_type=base_test_env["device"]
|
||||
device_type=base_test_env["device"],
|
||||
)
|
||||
|
||||
|
||||
trainer = Trainer(train_config)
|
||||
trainer.train()
|
||||
|
||||
|
||||
assert train_config.accumulation_steps == accumulation_steps
|
||||
|
||||
|
||||
def test_memory_efficient_training(base_test_env, random_dataset):
|
||||
"""Test training with memory-efficient configurations"""
|
||||
# Test with smaller batch sizes and gradient checkpointing
|
||||
small_batch_configs = [
|
||||
{"batch_size": 1, "accumulation_steps": 8},
|
||||
{"batch_size": 2, "accumulation_steps": 4},
|
||||
{"batch_size": 4, "accumulation_steps": 2}
|
||||
{"batch_size": 4, "accumulation_steps": 2},
|
||||
]
|
||||
|
||||
|
||||
for config in small_batch_configs:
|
||||
schedule_config = CosineScheduleConfig(
|
||||
warmup_steps=10,
|
||||
total_steps=20
|
||||
)
|
||||
schedule_config = CosineScheduleConfig(warmup_steps=10, total_steps=20)
|
||||
optimizer_fn = lambda model: torch.optim.AdamW(model.parameters())
|
||||
scheduler_fn = lambda optim: SchedulerFactory.load(optim, schedule_config)
|
||||
|
||||
|
||||
train_config = TrainConfig(
|
||||
strategy="seq",
|
||||
model=base_test_env["model"],
|
||||
dataset=random_dataset,
|
||||
optimizer_fn=optimizer_fn,
|
||||
scheduler_fn=scheduler_fn,
|
||||
checkpoint_dir=base_test_env["test_dir"],
|
||||
ckpt_dir=base_test_env["test_dir"],
|
||||
n_epoch=1,
|
||||
batch_size=config["batch_size"],
|
||||
checkpoint_interval=5,
|
||||
ckpt_interval=5,
|
||||
accumulation_steps=config["accumulation_steps"],
|
||||
max_grad_norm=1.0,
|
||||
random_seed=42,
|
||||
device_type=base_test_env["device"]
|
||||
device_type=base_test_env["device"],
|
||||
)
|
||||
|
||||
assert train_config.accumulation_steps == config["accumulation_steps"]
|
||||
|
||||
assert train_config.accumulation_steps == config["accumulation_steps"]
|
||||
|
||||
Reference in New Issue
Block a user