From 4f6fa57e88c2df27e87b12332b20499967a92649 Mon Sep 17 00:00:00 2001 From: Curnane Date: Tue, 28 Jul 2026 16:35:30 +0800 Subject: [PATCH] feat(domino): add Qwen3.5-4B managed-local NPU disaggregated recipe --- examples/README.md | 1 + .../qwen3.5-4b-domino-disaggregated-npu.yaml | 62 +++++++++++++++++++ tests/test_config/test_launch_topology.py | 36 ++++++++++- .../test_unified_feature_reachability.py | 2 +- 4 files changed, 99 insertions(+), 2 deletions(-) create mode 100644 examples/configs/qwen3.5-4b-domino-disaggregated-npu.yaml diff --git a/examples/README.md b/examples/README.md index be9b23903..092548ebc 100644 --- a/examples/README.md +++ b/examples/README.md @@ -38,6 +38,7 @@ NPU, offline, and managed/external-service variants, is in | `examples/configs/qwen3.5-4b-dflash-online-npu.yaml` | Disaggregated NPU SGLang capture | DFlash | | `examples/configs/qwen3.5-4b-dflash-disaggregated-npu.yaml` | Managed local NPU full stack | DFlash | | `examples/configs/qwen3.5-4b-domino-online-npu.yaml` | Disaggregated NPU SGLang capture | Domino | +| `examples/configs/qwen3.5-4b-domino-disaggregated-npu.yaml` | Managed local NPU full stack | Domino | Online configs point `data.train_data_path` at raw conversation data. Offline configs expect strategy-specific feature checkpoints in diff --git a/examples/configs/qwen3.5-4b-domino-disaggregated-npu.yaml b/examples/configs/qwen3.5-4b-domino-disaggregated-npu.yaml new file mode 100644 index 000000000..812bf1d47 --- /dev/null +++ b/examples/configs/qwen3.5-4b-domino-disaggregated-npu.yaml @@ -0,0 +1,62 @@ +# Single-node managed full stack for Ascend NPU: one `specforge train` command +# owns Mooncake, one patched SGLang capture server (device 0), and a 14-rank +# trainer (devices 2-15). Device ordinals are injected via +# ASCEND_RT_VISIBLE_DEVICES on Ascend hosts. +model: + target_model_path: "Qwen/Qwen3.5-4B" + draft_model_config: "configs/qwen3.5-4b-domino.json" + target_backend: sglang + trust_remote_code: true + embedding_key: "model.language_model.embed_tokens.weight" + torch_dtype: "bfloat16" + sglang_attention_backend: ascend +data: + train_data_path: "./cache/dataset/train_regen.jsonl" + max_length: 1024 + chat_template: "qwen3.5" + build_dataset_num_proc: 32 + cache_dir: "./cache" +training: + strategy: "domino" + num_epochs: 10 + batch_size: 1 + accumulation_steps: 4 + learning_rate: 0.0006 + warmup_ratio: 0.04 + max_grad_norm: 1 + attention_backend: "sdpa" + num_anchors: 16 + loss_decay_gamma: 7 + lambda_base_start: 1 + lambda_base_decay_ratio: 1 + save_interval: 3000 + log_interval: 50 + dist_timeout: 30 + seed: 42 +tracking: + report_to: "tensorboard" +run_id: "qwen3.5-4b-domino-npu-managed" +output_dir: "./outputs/qwen3.5-4b-domino-npu-managed" + +deployment: + mode: disaggregated + trainer: + nnodes: 1 + nproc_per_node: 14 + disaggregated: + control_dir: outputs/qwen3.5-4b-domino-npu-managed/control + consumer_state_dir: outputs/qwen3.5-4b-domino-npu-managed/consumer-state + backend: mooncake + managed_local: + # 16-card A3 layout: capture server owns device 0, trainer owns 2-15. + # On 8-card hosts use devices 1-7 with trainer.nproc_per_node: 7. + trainer_cuda_visible_devices: + ["2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15"] + mooncake: + # Host segment mounted by the capture-server sink for feature storage. + # 16 GiB fits a 64GB-card host next to the target model and KV cache. + global_segment_size_bytes: 17179869184 + capture_servers: + - port: 30000 + cuda_visible_devices: ["0"] + tp_size: 1 diff --git a/tests/test_config/test_launch_topology.py b/tests/test_config/test_launch_topology.py index 124cb479d..5a05470bf 100644 --- a/tests/test_config/test_launch_topology.py +++ b/tests/test_config/test_launch_topology.py @@ -68,6 +68,7 @@ "qwen3.5-35b-a3b-eagle3-online.yaml": 2, "qwen3.5-4b-dflash-online-npu.yaml": 8, "qwen3.5-4b-dflash-disaggregated-npu.yaml": 14, + "qwen3.5-4b-domino-disaggregated-npu.yaml": 14, "qwen3.5-4b-domino-online-npu.yaml": 8, "qwen3.6-27b-dflash-disaggregated.yaml": 2, "qwen3.6-27b-dflash-1server-dp2-disaggregated.yaml": 2, @@ -261,6 +262,39 @@ ], }, }, + "qwen3.5-4b-domino-disaggregated-npu.yaml": { + "control_dir": "outputs/qwen3.5-4b-domino-npu-managed/control", + "consumer_state_dir": "outputs/qwen3.5-4b-domino-npu-managed/consumer-state", + "backend": "mooncake", + "managed_local": { + "trainer_cuda_visible_devices": [ + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "10", + "11", + "12", + "13", + "14", + "15", + ], + "mooncake": { + "global_segment_size_bytes": 17179869184, + }, + "capture_servers": [ + { + "port": 30000, + "cuda_visible_devices": ["0"], + "tp_size": 1, + } + ], + }, + }, "qwen3.6-27b-dflash-disaggregated.yaml": { "control_dir": "outputs/qwen3.6-27b-dflash-disaggregated/control", "backend": "mooncake", @@ -347,7 +381,7 @@ def _recipes() -> dict[str, Path]: class ExampleLaunchTopologyTest(unittest.TestCase): def test_every_recipe_has_the_explicit_golden_topology(self): recipes = _recipes() - self.assertEqual(len(EXPECTED_NPROC_PER_NODE), 65) + self.assertEqual(len(EXPECTED_NPROC_PER_NODE), 66) self.assertEqual(set(recipes), set(EXPECTED_NPROC_PER_NODE)) for filename, nproc_per_node in EXPECTED_NPROC_PER_NODE.items(): diff --git a/tests/test_config/test_unified_feature_reachability.py b/tests/test_config/test_unified_feature_reachability.py index eba71be36..6c8c6e08c 100644 --- a/tests/test_config/test_unified_feature_reachability.py +++ b/tests/test_config/test_unified_feature_reachability.py @@ -146,7 +146,7 @@ def test_all_example_configs_validate_through_the_typed_entry(self): for path in EXAMPLE_CONFIG_DIR.glob("*.yaml") if not path.name.startswith(".") ) - self.assertEqual(len(paths), 65) + self.assertEqual(len(paths), 66) resolved_runs = { path.name: resolve_run(Config.from_file(str(path))) for path in paths