engine_flags.rs 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. //! Shared engine-specific argument builder — used by both the Docker
  2. //! (`docker/tail.rs`) and tmux (`tmux/commands.rs`) deployment paths to
  3. //! produce the model, recipe args, and distributed-rendezvous flags.
  4. //!
  5. //! This consolidates the duplicated flag logic that previously lived in
  6. //! `tail.rs` and `commands.rs` separately. Each deployment path still
  7. //! adds its own entrypoint and host/port flags on top.
  8. use crate::config::{DistributedBackend, Node, Recipe};
  9. /// Context for [`build_engine_args`] — the subset of `DockerCommandCtx`
  10. /// / `TmuxManager` state that the shared flag builder needs.
  11. pub struct EngineFlagsCtx<'a> {
  12. pub recipe: &'a Recipe,
  13. pub tp_size: u32,
  14. pub pp_size: u32,
  15. pub nnodes: u32,
  16. pub init_port: u16,
  17. pub master_ib_ip: &'a str,
  18. }
  19. /// Build the engine-specific arguments for a recipe: model path, recipe
  20. /// args, and distributed-rendezvous flags.
  21. ///
  22. /// Does **not** include the entrypoint, `--host`, or `--port` — those are
  23. /// deployment-specific and added by each path:
  24. /// - Docker (`tail.rs`): always adds `--host 0.0.0.0 --port <port>`.
  25. /// - tmux (`commands.rs`): adds `--port <port>` on the head node (or all
  26. /// nodes for SGLang).
  27. ///
  28. /// Single-node runs emit **no** distributed flags — the engine defaults
  29. /// to single-node. Multi-node runs get the full rendezvous flag set.
  30. pub fn build_engine_args(ctx: &EngineFlagsCtx<'_>, node: &Node) -> Vec<String> {
  31. let is_sglang = ctx.recipe.engine == "sglang";
  32. let is_distributed = matches!(
  33. ctx.recipe.distributed_backend,
  34. Some(DistributedBackend::Mp | DistributedBackend::Ray)
  35. );
  36. let mut args = Vec::new();
  37. // Model — SGLang uses --model-path, vLLM uses the bare model arg.
  38. // An empty model is valid for entrypoint-driven images: the image's own
  39. // entrypoint selects the model (e.g. via its MODEL_PATH env), so no
  40. // positional model is emitted (the launcher's own tail args still are).
  41. if !ctx.recipe.model.is_empty() {
  42. if is_sglang {
  43. args.push("--model-path".to_string());
  44. }
  45. args.push(ctx.recipe.model.clone());
  46. }
  47. // Recipe-specific args (e.g. --kv-cache-dtype, --max-model-len).
  48. args.extend(ctx.recipe.args.to_vec());
  49. // Distributed flags — only for multi-node runs.
  50. if ctx.nnodes > 1 {
  51. match ctx.recipe.distributed_backend {
  52. Some(DistributedBackend::Ray | DistributedBackend::Mp) if is_sglang => {
  53. args.extend(vec![
  54. "--tp".into(),
  55. ctx.tp_size.to_string(),
  56. "--nnodes".into(),
  57. ctx.nnodes.to_string(),
  58. "--node-rank".into(),
  59. node.rank.to_string(),
  60. "--dist-init-addr".into(),
  61. format!("{}:{}", ctx.master_ib_ip, ctx.init_port),
  62. ]);
  63. }
  64. Some(DistributedBackend::Ray | DistributedBackend::Mp) => {
  65. let mut fp = vec![
  66. "--tensor-parallel-size".into(),
  67. ctx.tp_size.to_string(),
  68. "--pipeline-parallel-size".into(),
  69. ctx.pp_size.to_string(),
  70. "--nnodes".into(),
  71. ctx.nnodes.to_string(),
  72. "--node-rank".into(),
  73. node.rank.to_string(),
  74. "--master-addr".into(),
  75. ctx.master_ib_ip.to_string(),
  76. "--master-port".into(),
  77. ctx.init_port.to_string(),
  78. ];
  79. if ctx.recipe.distributed_backend == Some(DistributedBackend::Ray) {
  80. fp.push("--distributed-executor-backend".into());
  81. fp.push("ray".into());
  82. }
  83. args.extend(fp);
  84. }
  85. _ => {}
  86. }
  87. }
  88. // vLLM workers in distributed mode run headless (no port binding).
  89. if ctx.nnodes > 1 && !is_sglang && node.rank > 0 && is_distributed {
  90. args.push("--headless".into());
  91. }
  92. args
  93. }
  94. #[cfg(test)]
  95. mod tests {
  96. use super::*;
  97. use crate::config::{DeployTarget, RecipeArgs, VenvSetup};
  98. use std::collections::HashMap;
  99. fn base_recipe() -> Recipe {
  100. Recipe {
  101. build_profile: None,
  102. engine: "vllm".into(),
  103. name: "T".into(),
  104. docker_image: "img".into(),
  105. model: "mdl".into(),
  106. port: 8000,
  107. required_nodes: None,
  108. pipeline_parallel_size: Some(1),
  109. tensor_parallel_size: 1,
  110. args: RecipeArgs::List(vec!["--x".into(), "4096".into()]),
  111. env: HashMap::new(),
  112. cluster: None,
  113. entrypoint: vec!["vllm".into(), "serve".into()],
  114. distributed_backend: Some(DistributedBackend::Mp),
  115. before_serve: vec![],
  116. after_stop: vec![],
  117. solo: None,
  118. deploy: DeployTarget::Docker,
  119. venv: None,
  120. venv_setup: VenvSetup::default(),
  121. venv_setup_script: None,
  122. mods: Default::default(),
  123. shm_size: String::new(),
  124. sync_image: true,
  125. docker_image_dir: None,
  126. extra_mounts: vec![],
  127. build: Default::default(),
  128. composed_model: None,
  129. }
  130. }
  131. fn tnode(rank: u32) -> Node {
  132. Node {
  133. name: format!("n{}", rank),
  134. ssh_ip: "10.0.0.1".into(),
  135. ib_ip: Some("10.0.0.1".into()),
  136. rank,
  137. host_key: None,
  138. entity: None,
  139. }
  140. }
  141. #[test]
  142. fn single_node_no_distributed_flags() {
  143. let r = base_recipe();
  144. let ctx = EngineFlagsCtx {
  145. recipe: &r,
  146. tp_size: 1,
  147. pp_size: 1,
  148. nnodes: 1,
  149. init_port: 25000,
  150. master_ib_ip: "10.0.0.1",
  151. };
  152. let args = build_engine_args(&ctx, &tnode(0));
  153. // model + recipe args, no distributed flags
  154. assert_eq!(args, vec!["mdl", "--x", "4096"]);
  155. }
  156. #[test]
  157. fn multi_node_vllm_mp_flags() {
  158. let r = base_recipe();
  159. let ctx = EngineFlagsCtx {
  160. recipe: &r,
  161. tp_size: 2,
  162. pp_size: 1,
  163. nnodes: 2,
  164. init_port: 25000,
  165. master_ib_ip: "10.0.0.1",
  166. };
  167. let head_args = build_engine_args(&ctx, &tnode(0));
  168. assert!(head_args.contains(&"--tensor-parallel-size".into()));
  169. assert!(head_args.contains(&"2".into()));
  170. assert!(head_args.contains(&"--nnodes".into()));
  171. assert!(head_args.contains(&"2".into()));
  172. assert!(head_args.contains(&"--master-addr".into()));
  173. assert!(head_args.contains(&"10.0.0.1".into()));
  174. assert!(head_args.contains(&"--master-port".into()));
  175. assert!(head_args.contains(&"25000".into()));
  176. // head node: no --headless
  177. assert!(!head_args.contains(&"--headless".into()));
  178. let worker_args = build_engine_args(&ctx, &tnode(1));
  179. assert!(worker_args.contains(&"--headless".into()));
  180. }
  181. #[test]
  182. fn multi_node_sglang_flags() {
  183. let mut r = base_recipe();
  184. r.engine = "sglang".into();
  185. let ctx = EngineFlagsCtx {
  186. recipe: &r,
  187. tp_size: 2,
  188. pp_size: 1,
  189. nnodes: 2,
  190. init_port: 25000,
  191. master_ib_ip: "10.0.0.1",
  192. };
  193. let args = build_engine_args(&ctx, &tnode(0));
  194. // SGLang uses --model-path, --tp, --dist-init-addr
  195. assert!(args.contains(&"--model-path".into()));
  196. assert!(args.contains(&"--tp".into()));
  197. assert!(args.contains(&"--dist-init-addr".into()));
  198. assert!(args.contains(&"10.0.0.1:25000".into()));
  199. // SGLang never gets --headless
  200. assert!(!args.contains(&"--headless".into()));
  201. }
  202. #[test]
  203. fn ray_backend_adds_executor() {
  204. let mut r = base_recipe();
  205. r.distributed_backend = Some(DistributedBackend::Ray);
  206. let ctx = EngineFlagsCtx {
  207. recipe: &r,
  208. tp_size: 1,
  209. pp_size: 1,
  210. nnodes: 2,
  211. init_port: 25000,
  212. master_ib_ip: "10.0.0.1",
  213. };
  214. let args = build_engine_args(&ctx, &tnode(0));
  215. assert!(args.contains(&"--distributed-executor-backend".into()));
  216. assert!(args.contains(&"ray".into()));
  217. }
  218. }