tail.rs 3.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. //! Builds the "tail" of the docker command — everything after the image
  2. //! name (entrypoint, model, args, auto-generated flags).
  3. use crate::config::{DistributedBackend, Node, Recipe};
  4. use super::shq;
  5. /// The subset of [`DockerCommandCtx`] that `build_tail` needs.
  6. pub(crate) struct TailCtx<'a> {
  7. pub recipe: &'a Recipe,
  8. pub tp_size: u32,
  9. pub pp_size: u32,
  10. pub nnodes: u32,
  11. pub init_port: u16,
  12. pub master_ib_ip: &'a str,
  13. }
  14. /// Build the docker command tail — everything after the image name.
  15. pub fn build_tail(ctx: &TailCtx<'_>, node: &Node) -> Vec<String> {
  16. let is_sglang = ctx.recipe.engine == "sglang";
  17. let is_distributed = matches!(
  18. ctx.recipe.distributed_backend,
  19. Some(DistributedBackend::Mp | DistributedBackend::Ray)
  20. );
  21. let mut tail = Vec::new();
  22. if !ctx.recipe.entrypoint.is_empty() {
  23. let mut ep = ctx.recipe.entrypoint.clone();
  24. if ctx.recipe.mods.commands.is_empty() && ep.len() >= 3 && ep[ep.len() - 2] == "-c" {
  25. let last = ep.len() - 1;
  26. ep[last] = shq(&ep[last]);
  27. }
  28. tail.extend(ep);
  29. }
  30. if is_sglang {
  31. tail.push("--model-path".to_string());
  32. }
  33. tail.push(ctx.recipe.model.clone());
  34. tail.extend(ctx.recipe.args.to_vec());
  35. // Single-node run (solo, required_nodes=1, or a single --node): emit
  36. // NO distributed flags — the engine defaults to single-node. Launching
  37. // one container with `--nnodes 1 --master-addr ...` is redundant at best
  38. // and rejected by some engines at worst. Multi-node runs get the full
  39. // rendezvous flag set below.
  40. let mut flags: Vec<String> = if ctx.nnodes > 1 {
  41. match ctx.recipe.distributed_backend {
  42. Some(DistributedBackend::Ray | DistributedBackend::Mp) if is_sglang => vec![
  43. "--tp".into(),
  44. ctx.tp_size.to_string(),
  45. "--nnodes".into(),
  46. ctx.nnodes.to_string(),
  47. "--node-rank".into(),
  48. node.rank.to_string(),
  49. "--dist-init-addr".into(),
  50. format!("{}:{}", ctx.master_ib_ip, ctx.init_port),
  51. ],
  52. Some(DistributedBackend::Ray | DistributedBackend::Mp) => {
  53. let mut fp = vec![
  54. "--tensor-parallel-size".into(),
  55. ctx.tp_size.to_string(),
  56. "--pipeline-parallel-size".into(),
  57. ctx.pp_size.to_string(),
  58. "--nnodes".into(),
  59. ctx.nnodes.to_string(),
  60. "--node-rank".into(),
  61. node.rank.to_string(),
  62. "--master-addr".into(),
  63. ctx.master_ib_ip.to_string(),
  64. "--master-port".into(),
  65. ctx.init_port.to_string(),
  66. ];
  67. if ctx.recipe.distributed_backend == Some(DistributedBackend::Ray) {
  68. fp.push("--distributed-executor-backend".into());
  69. fp.push("ray".into());
  70. }
  71. fp
  72. }
  73. _ => vec![],
  74. }
  75. } else {
  76. vec![]
  77. };
  78. if ctx.nnodes > 1 && !is_sglang && node.rank > 0 && is_distributed {
  79. flags.push("--headless".into());
  80. }
  81. tail.extend(flags);
  82. tail.push("--host".to_string());
  83. tail.push("0.0.0.0".to_string());
  84. tail.extend(["--port".into(), ctx.recipe.port.to_string()]);
  85. tail
  86. }