engine-drivers.md 5.9 KB

First-class engine drivers (EngineDriver trait)

Goal

Make third-party inference engines (gr8, DGPP, future ones) first-class engine values in a recipe — served/stopped/logged/health-probed/benched by the same sm121 commands as vllm/sglang — instead of hand-deployed outside the launcher. The operator directive: engines like dgpp/gr8 must be "равноправные члены наравне с vllm/sglang/tmux".

Current state (the scattered branches this replaces)

engine is a free String (default "vllm"); only vllm/sglang have any launcher logic, and it is duplicated across six sites, each branching on recipe.engine == "sglang":

Site Engine-specific behaviour
engine_flags.rs::build_engine_args model arg (--model-path vs positional), distributed rendezvous flags (vLLM set vs SGLang set), --headless for vLLM workers
docker/command.rs::collect_nccl_env worker-IP env: SGLANG_WORKER_IP vs VLLM_HOST_IP
tmux/commands.rs entrypoint fallback (python -m vllm… / python -m sglang…), port on all nodes (SGLang) vs head-only (vLLM)
tmux/mod.rs::build_host_ip_export host-IP env var per engine
doctor.rs install hint (pip install vllm / sglang)
publish_render.rs SGLang special-case in the rendered bundle

Design

Three orthogonal axes, each a first-class concept:

  1. engine → an EngineDriver (the inference-engine semantics).
  2. deploy → Docker | Tmux | Native (new) — how the process is started on a host (container / tmux session / bare SSH command).
  3. launch_shape → PerNode (every launch node runs the engine; vLLM, SGLang, gr8) | HeadCoordinated (rank 0 runs a cluster manager that brings up the peers itself; DGPP).

EngineDriver trait (in src/engine.rs)

// Head-coordination is the deploy target (`deploy = "native"` = rank 0 runs
// the engine's cluster manager), not a separate trait axis.
pub trait EngineDriver: Send + Sync {
    // Model argument (positional for vLLM, --model-path for SGLang,
    // --model for gr8, none for DGPP — the cluster JSON carries it).
    fn model_args(&self, recipe: &Recipe) -> Vec<String>;
    // Distributed rendezvous flags (vLLM/SGLang/gr8 sets; DGPP: none — the
    // JSON carries world_size + rendezvous).
    fn distributed_args(&self, ctx: &EngineFlagsCtx, node: &Node) -> Vec<String>;
    // Non-head workers run headless (vLLM yes; SGLang/gr8/DGPP no).
    fn headless_workers(&self) -> bool { false }
    // Env var carrying the node's bind IP (VLLM_HOST_IP / SGLANG_WORKER_IP / none).
    fn engine_ip_env(&self) -> Option<&'static str> { None }
    // Takes --host/--port (vLLM head, SGLang all). Engines with a fixed API
    // port (gr8 :8100, DGPP :18080) return false — no --host/--port, and
    // `validate` exempts them from the port-convention rule.
    fn takes_host_port(&self) -> bool { true }
    // tmux entrypoint fallback when the recipe sets none.
    fn entrypoint_default(&self) -> Vec<String>;
    // doctor install hint.
    fn install_hint(&self) -> &'static str;
}

A registry driver_for(engine: &str) -> &dyn EngineDriver maps the recipe's engine string to a driver (vllm, sglang, gr8, dgpp; unknown → the vLLM driver, matching today's default). build_engine_args and the five other sites delegate to it. Phase 1 is a pure refactor — vLLM/SGLang drivers reproduce the current behaviour byte-for-byte, all existing tests stay green.

EngineFlagsCtx gains peers

gr8 takes --peers <ip0>,<ip1>,… (the ordered launch-node IPs; each rank finds itself in the list). Add peers: &[String] to the ctx (vLLM/SGLang ignore it).

gr8 driver (kindlingai/gr8)

  • deploy = docker (image ghcr.io/kindlingai/gr8), per-node launch.
  • entrypoint ["/opt/gr8/gr8", "glm53", "up"]; model_args → --model <repo>; distributed_args → --tp <N> --peers <list>; no --headless, no engine-IP env, takes_host_port = false (API :8100 on the first peer).
  • Recipe carries --model-cache, --drafter, --shape in args; the vm.compaction_proactiveness=0 prerequisite is a before_serve host hook.
  • Stop: label-based docker stop works (the container is the engine).

DGPP driver (HawkBearPig/dgpp)

  • deploy = native (new), head-coordinated: rank 0 runs the manager.
  • The recipe's [dgpp] block carries http_port, dir (the DGPP checkout on the nodes), optional release, and a free-form [dgpp.engine] table. The launcher generates DGPP's cluster JSON (model, world_size, http, and the engine table serialized verbatim) plus a sibling .env (DGPP_NODES/DGPP_SSH_USER/ports), uploads both to <dir> on rank 0, and runs scripts/dgpp-cluster up --config <file> there (nohup, log under <dir>/log/). Stop runs scripts/dgpp-cluster down on rank 0.
  • Readiness is the uniform /v1/models probe on http_port (default 18080).

Phasing

  1. Trait + vLLM/SGLang drivers — pure refactor, six call-sites delegate, all tests green. ✅ (launcher engine: Phase 1 commit)
  2. gr8 driver + peers ctx + takes_host_port + a glm53-full-tp4-gr8 recipe; validate + dry-run render the exact gr8 glm53 up contract. ✅
  3. Native deploy (head-coordinated) + DGPP driver + [dgpp] config-JSON generation + a qwen38-27b-nvfp4-dgpp recipe; dry-run renders the JSON/.env upload + dgpp-cluster up/down. ✅ (native serve/stop live-tested on first real DGPP deploy)
  4. New-model recipes (Ling-3.1-flash, Step-5-Preview) — separate, gated on the bleeding-edge-base decision (nightly/dedicated image, 5–8 Sparks).

Constraints

  • Files ≤500 lines — engine.rs holds the trait + registry + the four drivers; split drivers into engine/gr8.rs etc. if it grows.
  • No #[allow] for clippy — refactor instead.
  • i18n: any new user-facing strings get keys in all six locales.