Przeglądaj źródła

flare driver: parse the package path from download output, controller-only cluster preflight

Two live defects found on the first gb10 launch: the driver took the last line of
'flare download' (a progress line) as the package path, so orbit run started with
garbage and the group died silently; and the preflight demanded the orbit cluster
file on peers, which only the controller has. Both fixed with tests (7 flare
tests). Night log for 2026-10-23/24 records the head build, the flat-package
requirement (HF snapshot symlinks break companion resolution) and the 1800 s CX7
transfer timeout on rank 3.
Alexander Korolev 1 dzień temu
rodzic
commit
56849d5137
2 zmienionych plików z 108 dodań i 12 usunięć
  1. 53 0
      history/night-shifts/2026-10-24.md
  2. 55 12
      src/native/flare.rs

+ 53 - 0
history/night-shifts/2026-10-24.md

@@ -0,0 +1,53 @@
+# Ночная смена 2026-10-23/24 — FLARE head + gb10
+
+## Сделано
+
+1. **Head-сборка FLARE** (исходники переехали в **NVIDIA/flare-local-inference**;
+   APT-пакет собран из `f2822dc3`, которого в новом репозитории нет — история
+   перезаписана). Собрано на dgx5: `uv sync --locked --extra prepare --extra test` +
+   `cargo build --release --manifest-path flare/Cargo.toml`, rev
+   `26646d8506049b44040227e795518e337e789c52` (2026-10-23 19:18 UTC), бинарь
+   20 MB, штамп `~/flare-head/.sm121-flare-build.json`. Бинарь разложен на
+   dgx6/7/8, агенты переустановлены (`flare agent install --accept-first`),
+   слушают 29900. Орбитальный кластер `sparks` из bring-up жив
+   (`gx10-dgx5..8`), пары пережили чистку.
+2. **Драйвер `engine = "flare"` доведён до запуска** (`src/native/flare.rs`):
+   preflight (контроллер — полный, пиры — только бинарь+агент), `flare download`,
+   `orbit run --detach`, `orbit stop`, `orbit attach` в `logs`, ожидание
+   `/health`+`/v1/models`, маппинг нод `[flare].orbit_nodes`.
+3. **Пакет gb10 скачан** (477 GiB = 511.3 GB, 18 файлов) на NAS в HF-кэш dgx5;
+   `EXIT=0`.
+4. **Найдены два реальных дефекта FLARE-пакета/обвязки** (для отчёта в лабу):
+   - `orbit run` с путём внутри `snapshots/<rev>/` (HF-дерево симлинков) падает:
+     `open companion "1ff3…safetensors": No such file or directory` — нужен
+     **плоский каталог** (сделал хардлинками: `/mnt/nas/ai/flare/pkgs/gb10`,
+     18 файлов, 477 GiB, `ln -f` на блобы — без копирования);
+   - драйвер брал последнюю строку вывода `flare download` как путь к пакету, а
+     там строки прогресса → группа запускалась с мусорным путём и молча умирала.
+     Исправлено (`parse_package_path` — ищет последнюю строку `*.safetensors` с
+     `/`), тест добавлен.
+
+## Где встали
+
+`orbit run` с плоским пакетом: rank 0 верифицирует пакет, ранги получают свои
+срезы по CX7, но **rank 3 упал через 1800 s**:
+`[FAILED] Receive rank package over CX7. 1800.5s` →
+`Error: "package channel ended inside a range"`, затем
+`timed out joining rank zero at 192.168.88.43:29800`. Пинги по фабрике
+(200.10/.23/.24) — 0 % потерь. То есть упёрлись в 30-минутный лимит передачи
+511 GB через NAS→фабрику (rank 0 читает пакет с NAS и раздаёт).
+
+## Следующие шаги (утро)
+
+1. Положить пакет на **локальный NVMe dgx5** (563 G свободно, пакет 477 GiB) —
+   чтение с NAS во время передачи, скорее всего, и держит скорость: сейчас
+   NAS читается всеми четырьмя процессами.
+2. Поднять `--transfer-streams` (по умолчанию 8) и/или перезапустить передачу
+   (`orbit run` идемпотентен по кэшу группы).
+3. Если снова таймаут — попробовать `--roce-chain` (в head появился
+   `3c80cb6d`: сервинг и подготовка по цепочке) вместо свитча.
+4. После готовности: `sm121 bench --recipe dsv41-flash-1017-4x-flare-gb10
+   --cluster dgx58-switch` и сравнение с vLLM-лейном (91/100, pp 2,927) и
+   прод-SGLang (47.8–50.7 / 64.2–68.3).
+5. Дописать в драйвер: сборку плоского каталога из snapshot (хардлинки) вместо
+   ожидания, что оператор сделает руками; `--transfer-streams` в `[flare].serve_flags`.

+ 55 - 12
src/native/flare.rs

@@ -148,13 +148,37 @@ pub fn attach_cmd(config: &Config, recipe: &Recipe) -> Result<String> {
 /// Preflight on every launch node: the agent installed and the orbit cluster
 /// visible. Runs before any download or launch — a peer that never paired
 /// otherwise fails minutes into a 500 GB fetch.
-pub fn preflight_cmd(config: &Config) -> String {
-    format!(
-        "{} --version >/dev/null && systemctl --user is-active flare-agent >/dev/null && {} orbit status --cluster {} >/dev/null",
-        binary(config),
-        binary(config),
-        shq(&orbit_cluster(config)),
-    )
+///
+/// Only the controller has the orbit file with `[cluster.<name>]` — a peer
+/// just needs the binary and a live agent, so the cluster check is added
+/// there and nowhere else.
+pub fn preflight_cmd(config: &Config, controller: bool) -> String {
+    let base = format!(
+        "{} --version >/dev/null && systemctl --user is-active flare-agent >/dev/null",
+        binary(config)
+    );
+    if controller {
+        format!(
+            "{base} && {} orbit status --cluster {} >/dev/null",
+            binary(config),
+            shq(&orbit_cluster(config)),
+        )
+    } else {
+        base
+    }
+}
+
+/// Pick the package path out of `flare download` output: it prints progress
+/// lines ("Downloading model file: …") and finally the resolved package path
+/// ending in `.safetensors`. Taking the last line blindly handed `orbit run`
+/// a progress line and the group died silently — so scan backwards for a path.
+pub fn parse_package_path(stdout: &str) -> Option<String> {
+    stdout
+        .lines()
+        .rev()
+        .map(str::trim)
+        .find(|l| l.ends_with(".safetensors") && l.contains('/'))
+        .map(str::to_string)
 }
 
 fn head(cluster: &crate::config::Cluster) -> Result<&Node> {
@@ -175,15 +199,16 @@ pub async fn serve(
     dry_run: bool,
 ) -> Result<()> {
     let controller = head(cluster)?;
-    let pre = preflight_cmd(config);
     if dry_run {
         println!(
-            "  [dry-run] preflight on {} nodes: {pre}",
-            cluster.nodes.len()
+            "  [dry-run] preflight on {} nodes (cluster check on the controller only): {}",
+            cluster.nodes.len(),
+            preflight_cmd(config, true)
         );
     } else {
         for node in &cluster.nodes {
-            if let Err(e) = crate::ssh::run_ssh(config, node, pre.clone(), false).await {
+            let pre = preflight_cmd(config, node.rank == 0);
+            if let Err(e) = crate::ssh::run_ssh(config, node, pre, false).await {
                 bail!(
                     "FLARE preflight failed on {}: {e} — install `flare`, then `flare agent install` and pair the node with the controller",
                     node.name
@@ -343,9 +368,27 @@ mod tests {
 
     #[test]
     fn preflight_checks_agent_and_pairing() {
-        let cmd = preflight_cmd(&cfg());
+        let cmd = preflight_cmd(&cfg(), true);
         assert!(cmd.contains("is-active flare-agent"), "{cmd}");
         assert!(cmd.contains("orbit status --cluster 'sparks'"), "{cmd}");
+        // a peer has no orbit file: binary + agent only
+        assert!(!preflight_cmd(&cfg(), false).contains("orbit status"));
+    }
+
+    #[test]
+    fn package_path_is_taken_from_the_download_output() {
+        let out = "Resolved package: org/repo@abc / pkg.safetensors\n\
+                   Downloading model file: aa.safetensors...\n\
+                   Downloading model file: bb.safetensors...\n\
+                   /mnt/nas/hub/snapshots/abc/pkg.safetensors\n";
+        assert_eq!(
+            parse_package_path(out).as_deref(),
+            Some("/mnt/nas/hub/snapshots/abc/pkg.safetensors")
+        );
+        assert_eq!(
+            parse_package_path("Downloading model file: x.safetensors..."),
+            None
+        );
     }
 
     #[test]