spec-b300 / source /tests /unit /scripts /test_infinity_parser2_two_node_launch.py
khazic's picture
Archive three-epoch run: logs and provenance part 4
c34ff1f verified
Raw
History Blame Contribute Delete
6.74 kB
"""Parser2 DFlash2 双节点入口:验证实际命令和完整词表,不启动 GPU 进程。"""
import argparse
import json
from pathlib import Path
import numpy as np
from torch.distributed.run import get_args_parser
from speculators.train.cli import parse_vocab_mappings
from speculators.train.config import TrainConfig
from tests.unit.scripts.test_qwen3_6_two_node_launch import (
NETWORK_ENV,
SCRIPT_DIR,
assert_node_caches,
assert_teacher_stopped,
flag,
read_capture,
run_nodes,
)
from tests.unit.scripts.test_qwen3_6_two_node_launch import (
launch_env as launch_env, # noqa: PLC0414 复用模拟启动 fixture。
)
SCRIPT = SCRIPT_DIR / "dflash2_infinity_parser2_flash_online_2node.sh"
def test_parser2_two_node_launch(launch_env, monkeypatch):
env = launch_env
# 使用真实目录结构,确认传入的是 prepared Arrow 数据而不是 regen 根目录。
env.pop("DATA_DIR")
data = (
Path(env["ROOT"])
/ "datasets/infinity_parsers2_v2_1_max32768_vocab32k/dflash_data/full"
)
data.mkdir(parents=True)
for name in ("state.json", "dataset_info.json"):
(data / name).write_text("{}")
d2t = np.zeros(248320, dtype=np.int64)
t2d = np.ones(248320, dtype=np.bool_)
np.save(data / "d2t.npy", d2t)
np.save(data / "t2d.npy", t2d)
(Path(env["MODEL"]) / "config.json").write_text(
json.dumps({"model_type": "qwen3_5", "text_config": {"vocab_size": 248320}})
)
env["NNODE_TEST_EXPECT_NODES"] = "2"
for output, returncode in run_nodes("dflash2", env, (0, 1), script=SCRIPT):
assert returncode == 0, output
configs = []
for rank in (0, 1):
teacher = read_capture(env, "vllm", rank)
train = read_capture(env, "train", rank)
assert teacher["env"]["CUDA_VISIBLE_DEVICES"] == "7,6"
assert train["env"]["CUDA_VISIBLE_DEVICES"] == "5,4,3,2,1,0"
assert "VLLM_MEDIA_LOADING_THREAD_COUNT" not in teacher["env"]
assert "--api-server-count" not in teacher["argv"]
for record in (teacher, train):
for name in ("RANK", "WORLD_SIZE", "LOCAL_RANK", "LOCAL_WORLD_SIZE"):
assert name not in record["env"]
for name, value in NETWORK_ENV.items():
assert record["env"][name] == value
assert "MASTER_ADDR" not in teacher["env"]
assert "MASTER_PORT" not in teacher["env"]
for name, value in {
"--tensor-parallel-size": "1",
"--data-parallel-size": "2",
"--data-parallel-backend": "mp",
"--nnodes": "1",
"--node-rank": "0",
"--master-addr": "127.0.0.1",
"--data-parallel-address": "127.0.0.1",
"--max-model-len": "65536",
"--mm-processor-cache-gb": "0",
"--served-model-name": env["MODEL"],
}.items():
assert flag(teacher["argv"], name) == value
monkeypatch.setenv("PET_NPROC_PER_NODE", "8")
distributed = get_args_parser().parse_args(train["argv"])
assert distributed.nnodes == "2"
assert distributed.nproc_per_node == "6"
assert distributed.node_rank == rank
assert distributed.master_addr == env["MASTER_ADDR"]
assert str(distributed.master_port) == env["MASTER_PORT"]
assert distributed.rdzv_backend == "static"
assert not distributed.standalone
assert not distributed.no_python
cfg = TrainConfig.resolve(distributed.training_script_args).flatten()
configs.append(cfg)
assert cfg["run_name"] == "dflash2-parser2_1-2node"
run_dir = (
Path(env["ROOT"])
/ "model_weights/dflash2_parser2_1_flash_2node"
/ cfg["run_name"]
)
assert cfg["save_path"] == str(run_dir / "checkpoints")
assert cfg["log_dir"] == str(run_dir)
assert_node_caches(teacher, train, env, rank, cfg["run_name"])
for name, value in {
"speculator_type": "dflash2",
"checkpoint_freq": 0.1,
"verifier_name_or_path": env["MODEL"],
"data_path": str(data),
"draft_vocab_size": None,
"draft_arch": "qwen3",
"num_layers": 5,
"mask_token_id": 248077,
"target_layer_ids": [2, 7, 12, 17, 22],
"draft_mrope_full_head_hack": True,
"sliding_window": 2048,
"sliding_window_non_causal": True,
"full_attention_indices": [],
"total_seq_len": 16384,
"block_size": 16,
"max_anchors": 1024,
"sample_from_anchor": None,
"loss_fn": "ce",
"per_position_loss_weight": "dpace",
"conv_kernel_size": 2,
"conv_group_size": 16,
"selector_rank": 256,
"selector_top_k": 16,
"selector_loss_alpha": 0.1,
"num_workers": 12,
"prefetch_factor": 4,
"fetch_threads": 1,
"dataloader_in_order": True,
"vllm_http_keepalive": True,
"request_timeout": 120,
"max_retries": 3,
"generation_validation_retries": 2,
"max_consecutive_generation_failures": 20,
"fail_on_hidden_state_error": False,
}.items():
assert cfg[name] == value, name
assert cfg["vllm_endpoint"] == f"http://127.0.0.1:{env['VLLM_PORT']}/v1"
loaded_d2t, loaded_t2d, vocab_size = parse_vocab_mappings(
argparse.Namespace(**cfg)
)
assert vocab_size == 248320
np.testing.assert_array_equal(loaded_d2t.numpy(), d2t)
np.testing.assert_array_equal(loaded_t2d.numpy(), t2d)
hs_path = Path(cfg["hidden_states_path"])
assert hs_path.parent == Path("/tmp")
assert not hs_path.exists() # 脚本退出时清理本次目录。
assert str(hs_path) == flag(teacher["argv"], "--hidden-states-path")
assert_teacher_stopped(teacher)
for key in ("save_path", "run_name", "log_dir"):
assert configs[0][key] == configs[1][key]
assert configs[0]["hidden_states_path"] != configs[1]["hidden_states_path"]
# 训练直接加载全词表映射,不改写数据目录。
np.testing.assert_array_equal(np.load(data / "d2t.npy"), d2t)
np.testing.assert_array_equal(np.load(data / "t2d.npy"), t2d)
def test_missing_parser2_prepared_data_fails_before_teacher(launch_env):
launch_env.pop("DATA_DIR")
[(output, returncode)] = run_nodes("dflash2", launch_env, (0,), script=SCRIPT)
assert returncode != 0
assert "dflash_data/full/state.json" in output
assert not list(Path(launch_env["NNODE_TEST_CAPTURE"]).iterdir())