pre_serve.rs 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. //! Types for the `before_serve` configuration and `exec_aliases` table.
  2. //!
  3. //! Lives outside `config.rs` so the latter stays under the 500-line limit
  4. //! the project enforces (see AGENTS.md).
  5. use serde::Deserialize;
  6. /// Behaviour on a non-zero exit code for a [`PreServeCommand`].
  7. #[derive(Clone, Copy, Debug, PartialEq, Eq, Default, Deserialize)]
  8. #[serde(rename_all = "lowercase")]
  9. pub enum OnFailure {
  10. #[default]
  11. Abort,
  12. Warn,
  13. Continue,
  14. }
  15. /// One entry in a `before_serve` / `after_stop` / `mods` block.
  16. ///
  17. /// Three mutually exclusive forms (untagged, so TOML picks by shape):
  18. /// - `Bare(String)` — a raw shell command, run as-is over SSH.
  19. /// - `Configured { command, on_failure }` — a raw shell command with an
  20. /// explicit `on_failure` policy.
  21. /// - `Script { script, on_failure }` — a built-in script referenced by
  22. /// alias (e.g. `stop_dgx_services`). The script body is baked into the
  23. /// binary at compile time (see [`crate::builtin_scripts`]) and uploaded
  24. /// + run via SSH on each node — no file copy from the operator's machine.
  25. ///
  26. /// Unknown aliases error at resolution time with the list of known ones.
  27. #[derive(Clone, Debug, Deserialize)]
  28. #[serde(untagged)]
  29. pub enum PreServeCommand {
  30. Bare(String),
  31. Configured {
  32. command: String,
  33. #[serde(default)]
  34. on_failure: OnFailure,
  35. },
  36. Script {
  37. script: String,
  38. #[serde(default)]
  39. on_failure: OnFailure,
  40. },
  41. }
  42. impl PreServeCommand {
  43. /// For `Bare`/`Configured`, the raw shell command. For `Script`, the
  44. /// alias (NOT the script body) — use [`resolve`] to get the body.
  45. pub fn command(&self) -> &str {
  46. match self {
  47. PreServeCommand::Bare(c) => c,
  48. PreServeCommand::Configured { command, .. } => command,
  49. PreServeCommand::Script { script, .. } => script,
  50. }
  51. }
  52. pub fn on_failure(&self) -> OnFailure {
  53. match self {
  54. PreServeCommand::Bare(_) => OnFailure::default(),
  55. PreServeCommand::Configured { on_failure, .. } => *on_failure,
  56. PreServeCommand::Script { on_failure, .. } => *on_failure,
  57. }
  58. }
  59. }
  60. #[derive(Clone, Debug, Default, Deserialize)]
  61. pub struct BeforeServeTable {
  62. #[serde(default)]
  63. pub commands: Vec<PreServeCommand>,
  64. }
  65. #[derive(Clone, Debug, Default, Deserialize)]
  66. pub struct ModsTable {
  67. #[serde(default)]
  68. pub commands: Vec<PreServeCommand>,
  69. }
  70. /// Distributed execution backend — `mp` or `ray`. Absent means single-node.
  71. #[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize)]
  72. #[serde(rename_all = "lowercase")]
  73. pub enum DistributedBackend {
  74. Mp,
  75. Ray,
  76. }
  77. /// Recipe arguments — either an inline TOML string (split by whitespace) or
  78. /// a `Vec<String>` list. Accepted in TOML as either:
  79. ///
  80. /// ```toml
  81. /// args = """
  82. /// --gpu-memory-utilization 0.90
  83. /// --max-model-len 262144
  84. /// """
  85. /// ```
  86. ///
  87. /// ```toml
  88. /// args = ["--gpu-memory-utilization", "0.90"]
  89. /// ```
  90. #[derive(Clone, Debug, Deserialize)]
  91. #[serde(untagged)]
  92. pub enum RecipeArgs {
  93. List(Vec<String>),
  94. Inline(String),
  95. }
  96. impl Default for RecipeArgs {
  97. fn default() -> Self {
  98. RecipeArgs::List(Vec::new())
  99. }
  100. }
  101. impl RecipeArgs {
  102. pub fn to_vec(&self) -> Vec<String> {
  103. match self {
  104. RecipeArgs::Inline(s) => s.split_whitespace().map(String::from).collect(),
  105. RecipeArgs::List(v) => v.clone(),
  106. }
  107. }
  108. /// Replace all occurrences of `placeholder` with `replacement` in each
  109. /// arg string. Used to substitute model path references (e.g.
  110. /// `{{mtp_draft}}`) with resolved HF cache paths per-node.
  111. pub fn substitute(&self, placeholder: &str, replacement: &str) -> RecipeArgs {
  112. match self {
  113. RecipeArgs::Inline(s) => RecipeArgs::Inline(s.replace(placeholder, replacement)),
  114. RecipeArgs::List(v) => RecipeArgs::List(
  115. v.iter()
  116. .map(|a| a.replace(placeholder, replacement))
  117. .collect(),
  118. ),
  119. }
  120. }
  121. }
  122. /// Venv setup commands — either an inline multi-line TOML string (one
  123. /// command per line) or a `Vec<String>` list. Accepted in TOML as either:
  124. ///
  125. /// ```toml
  126. /// venv_setup = """
  127. /// uv venv ~/venvs/vllm025 -p 3.12
  128. /// uv pip install vllm==0.25.1 --torch-backend=cu130
  129. /// """
  130. /// ```
  131. ///
  132. /// ```toml
  133. /// venv_setup = [
  134. /// "uv venv ~/venvs/vllm025 -p 3.12",
  135. /// "uv pip install vllm==0.25.1 --torch-backend=cu130",
  136. /// ]
  137. /// ```
  138. ///
  139. /// Blank lines and lines starting with `#` are skipped (shell comments).
  140. #[derive(Clone, Debug, Deserialize)]
  141. #[serde(untagged)]
  142. pub enum VenvSetup {
  143. List(Vec<String>),
  144. Inline(String),
  145. }
  146. impl Default for VenvSetup {
  147. fn default() -> Self {
  148. VenvSetup::List(Vec::new())
  149. }
  150. }
  151. impl VenvSetup {
  152. /// Expand to a list of commands. For `Inline`, each non-blank,
  153. /// non-comment line becomes one command. For `List`, each entry is
  154. /// a command as-is.
  155. pub fn to_vec(&self) -> Vec<String> {
  156. match self {
  157. VenvSetup::List(v) => v.clone(),
  158. VenvSetup::Inline(s) => s
  159. .lines()
  160. .map(|l| l.trim().to_string())
  161. .filter(|l| !l.is_empty() && !l.starts_with('#'))
  162. .collect(),
  163. }
  164. }
  165. }
  166. impl std::fmt::Display for DistributedBackend {
  167. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  168. match self {
  169. DistributedBackend::Mp => write!(f, "mp"),
  170. DistributedBackend::Ray => write!(f, "ray"),
  171. }
  172. }
  173. }