solo_tests.rs 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. //! Solo / single-node flag-emission tests — split from `tests.rs` to keep
  2. //! that file under the 500-line limit. Uses the parent module's `trec`,
  3. //! `tcls`, `make_dm_ctx`, and `build_docker_command` helpers.
  4. use super::*;
  5. use crate::config::Recipe;
  6. #[test]
  7. fn single_node_run_omits_distributed_flags() {
  8. // A solo or required_nodes=1 run must NOT emit --nnodes/--tp/--master-addr
  9. // etc. — the engine defaults to single-node. (Regression for the bug
  10. // where a 1-node recipe on a multi-node cluster launched N containers each
  11. // told `--nnodes N --tp N`.)
  12. let mut r = trec();
  13. r.required_nodes = Some(1);
  14. r.tensor_parallel_size = 1;
  15. r.pipeline_parallel_size = Some(1);
  16. // nnodes=1 is what start_cluster computes after trimming to required_nodes.
  17. let ctx = make_dm_ctx(&r, 1, 1, 1, false);
  18. let cmd = build_docker_command(&ctx, &tcls().nodes[0], true)
  19. .unwrap()
  20. .join(" ");
  21. assert!(
  22. !cmd.contains("--nnodes"),
  23. "single-node must not pass --nnodes: {cmd}"
  24. );
  25. assert!(
  26. !cmd.contains("--tensor-parallel-size"),
  27. "single-node must not pass --tp: {cmd}"
  28. );
  29. assert!(
  30. !cmd.contains("--pipeline-parallel-size"),
  31. "single-node must not pass --pp: {cmd}"
  32. );
  33. assert!(
  34. !cmd.contains("--master-addr"),
  35. "single-node must not pass --master-addr: {cmd}"
  36. );
  37. assert!(
  38. !cmd.contains("--master-port"),
  39. "single-node must not pass --master-port: {cmd}"
  40. );
  41. assert!(
  42. !cmd.contains("--node-rank"),
  43. "single-node must not pass --node-rank: {cmd}"
  44. );
  45. assert!(
  46. !cmd.contains("--headless"),
  47. "single-node must not pass --headless: {cmd}"
  48. );
  49. // But the engine still gets --host/--port.
  50. assert!(cmd.contains("--host"));
  51. assert!(cmd.contains("--port"));
  52. }
  53. #[test]
  54. fn multi_node_run_still_emits_distributed_flags() {
  55. // Regression guard: the single-node fix must not break the multi-node path.
  56. let r = trec(); // required_nodes=2, tp collapses to nnodes=2
  57. let ctx = make_dm_ctx(&r, 2, 2, 2, false);
  58. let cmd = squash(
  59. &build_docker_command(&ctx, &tcls().nodes[0], true)
  60. .unwrap()
  61. .join(" "),
  62. );
  63. assert!(cmd.contains("--nnodes 2"));
  64. assert!(cmd.contains("--tensor-parallel-size 2"));
  65. assert!(cmd.contains("--pipeline-parallel-size 2"));
  66. assert!(cmd.contains("--master-addr"));
  67. assert!(cmd.contains("--master-port"));
  68. assert!(cmd.contains("--node-rank 0"));
  69. }
  70. #[test]
  71. fn solo_worker_node_emits_headless_no_rendezvous() {
  72. // Multi-node: a worker (rank>0) gets --headless; single-node never does.
  73. let r = trec();
  74. let ctx = make_dm_ctx(&r, 2, 2, 2, false);
  75. let worker_cmd = build_docker_command(&ctx, &tcls().nodes[1], false)
  76. .unwrap()
  77. .join(" ");
  78. assert!(
  79. worker_cmd.contains("--headless"),
  80. "multi-node worker needs --headless: {worker_cmd}"
  81. );
  82. // Single-node worker (n/a really, but guard): headless never appears.
  83. let mut r1 = r.clone();
  84. r1.required_nodes = Some(1);
  85. let ctx1 = make_dm_ctx(&r1, 1, 1, 1, false);
  86. let solo_cmd = build_docker_command(&ctx1, &tcls().nodes[1], false)
  87. .unwrap()
  88. .join(" ");
  89. assert!(!solo_cmd.contains("--headless"));
  90. }
  91. #[test]
  92. fn recipe_solo_field_deserializes() {
  93. let toml_str = r#"
  94. name = "Solo"
  95. model = "org/m"
  96. solo = true
  97. required_nodes = 4
  98. "#;
  99. let r: Recipe = toml::from_str(toml_str).unwrap();
  100. assert_eq!(r.solo, Some(true));
  101. // unset → None (default multi-node)
  102. let toml2 = r#"
  103. name = "Multi"
  104. model = "org/m"
  105. required_nodes = 4
  106. "#;
  107. let r2: Recipe = toml::from_str(toml2).unwrap();
  108. assert_eq!(r2.solo, None);
  109. }