Selaa lähdekoodia

bench: fix pump reading into empty buffer (EOF→EPIPE→silent exit 120)

Alexander Korolev 1 kuukausi sitten
vanhempi
sitoutus
52e1dc891d
25 muutettua tiedostoa jossa 59 lisäystä ja 18 poistoa
  1. 1 1
      docker-images/deepseek-v4-flash-dspark-src/build.sh
  2. 1 1
      docker-images/deepseek-v4-flash-dspark/build.sh
  3. 1 1
      docker-images/deepseek-v4-flash-dspark38/build.sh
  4. 1 1
      docker-images/deepseek-v4-flash-eugr/build.sh
  5. 1 1
      docker-images/dsv4-flash-vision/build.sh
  6. 1 1
      docker-images/glm52-nf3-hybrid/build.sh
  7. 1 1
      docker-images/glm52/build.sh
  8. 1 1
      docker-images/glm53-flash-aiden/build.sh
  9. 1 1
      docker-images/glm53-flash-dflash2/build.sh
  10. 1 1
      docker-images/glm53-flash-exl3/build.sh
  11. 1 1
      docker-images/glm53-flash-nvfp4-4p67/build.sh
  12. 1 1
      docker-images/glm53-flash-nvfp4-lab/build.sh
  13. 1 1
      docker-images/glm53-flash-nvfp4/build.sh
  14. 1 1
      docker-images/qwen38-flashnext-nvfp4/build.sh
  15. 1 1
      docker-images/solar-open2-nvfp4/build.sh
  16. 0 0
      recipes/glm53-flash-r28-4x-dcp4/reports/2026-09-08_182306_r28-4x-dcp4.log
  17. 0 0
      recipes/glm53-flash-r28-4x-dcp4/reports/2026-09-08_182734_r28-4x-dcp4.log
  18. 0 0
      recipes/glm53-flash-r28-4x-dcp4/reports/2026-09-08_183637_r28-4x-dcp4.log
  19. 0 0
      recipes/glm53-flash-r28-4x-dcp4/reports/2026-09-08_183747_dbg.log
  20. 0 0
      recipes/glm53-flash-r28-4x-dcp4/reports/2026-09-08_184030_dbg2.log
  21. 0 0
      recipes/glm53-flash-r28-4x-dcp4/reports/2026-09-08_184035_dbg3.log
  22. 0 0
      recipes/glm53-flash-r28-4x-dcp4/reports/2026-09-08_184116_dbg4.log
  23. 0 0
      recipes/glm53-flash-r28-4x-dcp4/reports/2026-09-08_184347_dbg5.log
  24. 0 0
      recipes/glm53-flash-r28-4x-dcp4/reports/2026-09-08_184442_dbg6.log
  25. 44 3
      src/bench.rs

+ 1 - 1
docker-images/deepseek-v4-flash-dspark-src/build.sh

@@ -26,7 +26,7 @@ echo "=== Building ${TAG} ==="
 # version-independent stages (apt, torch, CUDA base) are reused across
 # rebuilds — only the pinned source stages (vllm/b12x/…) re-download and
 # re-compile. Override the dir with SM121_BUILDKIT_CACHE if desired.
-CACHE_DIR="${SM121_BUILDKIT_CACHE:-$HOME/sm121-cache/buildkit}"
+CACHE_DIR="${SM121_BUILDKIT_CACHE:-$HOME/.cache/sm121-buildkit}"
 mkdir -p "${CACHE_DIR}"
 docker buildx build "${ARGS[@]}" --cache-from "type=local,src=${CACHE_DIR}" \
     --cache-to "type=local,dest=${CACHE_DIR},mode=max" \

+ 1 - 1
docker-images/deepseek-v4-flash-dspark/build.sh

@@ -8,7 +8,7 @@ echo "=== Building ${TAG} ==="
 # version-independent stages (apt, torch, CUDA base) are reused across
 # rebuilds — only the pinned source stages (vllm/b12x/…) re-download and
 # re-compile. Override the dir with SM121_BUILDKIT_CACHE if desired.
-CACHE_DIR="${SM121_BUILDKIT_CACHE:-$HOME/sm121-cache/buildkit}"
+CACHE_DIR="${SM121_BUILDKIT_CACHE:-$HOME/.cache/sm121-buildkit}"
 mkdir -p "${CACHE_DIR}"
 docker buildx build --cache-from "type=local,src=${CACHE_DIR}" \
     --cache-to "type=local,dest=${CACHE_DIR},mode=max" \

+ 1 - 1
docker-images/deepseek-v4-flash-dspark38/build.sh

@@ -8,7 +8,7 @@ echo "=== Building ${TAG} ==="
 # version-independent stages (apt, torch, CUDA base) are reused across
 # rebuilds — only the pinned source stages (vllm/b12x/…) re-download and
 # re-compile. Override the dir with SM121_BUILDKIT_CACHE if desired.
-CACHE_DIR="${SM121_BUILDKIT_CACHE:-$HOME/sm121-cache/buildkit}"
+CACHE_DIR="${SM121_BUILDKIT_CACHE:-$HOME/.cache/sm121-buildkit}"
 mkdir -p "${CACHE_DIR}"
 docker buildx build --cache-from "type=local,src=${CACHE_DIR}" \
     --cache-to "type=local,dest=${CACHE_DIR},mode=max" \

+ 1 - 1
docker-images/deepseek-v4-flash-eugr/build.sh

@@ -10,7 +10,7 @@ echo "=== Building ${TAG} ==="
 # version-independent stages (apt, torch, CUDA base) are reused across
 # rebuilds — only the pinned source stages (vllm/b12x/…) re-download and
 # re-compile. Override the dir with SM121_BUILDKIT_CACHE if desired.
-CACHE_DIR="${SM121_BUILDKIT_CACHE:-$HOME/sm121-cache/buildkit}"
+CACHE_DIR="${SM121_BUILDKIT_CACHE:-$HOME/.cache/sm121-buildkit}"
 mkdir -p "${CACHE_DIR}"
 docker buildx build --cache-from "type=local,src=${CACHE_DIR}" \
     --cache-to "type=local,dest=${CACHE_DIR},mode=max" \

+ 1 - 1
docker-images/dsv4-flash-vision/build.sh

@@ -24,7 +24,7 @@ echo "=== Building ${TAG} ==="
 # version-independent stages (apt, torch, CUDA base) are reused across
 # rebuilds — only the pinned source stages (vllm/b12x/…) re-download and
 # re-compile. Override the dir with SM121_BUILDKIT_CACHE if desired.
-CACHE_DIR="${SM121_BUILDKIT_CACHE:-$HOME/sm121-cache/buildkit}"
+CACHE_DIR="${SM121_BUILDKIT_CACHE:-$HOME/.cache/sm121-buildkit}"
 mkdir -p "${CACHE_DIR}"
 docker buildx build "${ARGS[@]}" --cache-from "type=local,src=${CACHE_DIR}" \
     --cache-to "type=local,dest=${CACHE_DIR},mode=max" \

+ 1 - 1
docker-images/glm52-nf3-hybrid/build.sh

@@ -26,7 +26,7 @@ echo "=== Building ${TAG} ==="
 # version-independent stages (apt, torch, CUDA base) are reused across
 # rebuilds — only the pinned source stages (vllm/b12x/…) re-download and
 # re-compile. Override the dir with SM121_BUILDKIT_CACHE if desired.
-CACHE_DIR="${SM121_BUILDKIT_CACHE:-$HOME/sm121-cache/buildkit}"
+CACHE_DIR="${SM121_BUILDKIT_CACHE:-$HOME/.cache/sm121-buildkit}"
 mkdir -p "${CACHE_DIR}"
 docker buildx build "${ARGS[@]}" --cache-from "type=local,src=${CACHE_DIR}" \
     --cache-to "type=local,dest=${CACHE_DIR},mode=max" \

+ 1 - 1
docker-images/glm52/build.sh

@@ -9,7 +9,7 @@ echo "=== Building ${TAG} ==="
 # version-independent stages (apt, torch, CUDA base) are reused across
 # rebuilds — only the pinned source stages (vllm/b12x/…) re-download and
 # re-compile. Override the dir with SM121_BUILDKIT_CACHE if desired.
-CACHE_DIR="${SM121_BUILDKIT_CACHE:-$HOME/sm121-cache/buildkit}"
+CACHE_DIR="${SM121_BUILDKIT_CACHE:-$HOME/.cache/sm121-buildkit}"
 mkdir -p "${CACHE_DIR}"
 docker buildx build --cache-from "type=local,src=${CACHE_DIR}" \
     --cache-to "type=local,dest=${CACHE_DIR},mode=max" \

+ 1 - 1
docker-images/glm53-flash-aiden/build.sh

@@ -7,7 +7,7 @@ echo "=== Building ${TAG} ==="
 # version-independent stages (apt, torch, CUDA base) are reused across
 # rebuilds — only the pinned source stages (vllm/b12x/…) re-download and
 # re-compile. Override the dir with SM121_BUILDKIT_CACHE if desired.
-CACHE_DIR="${SM121_BUILDKIT_CACHE:-$HOME/sm121-cache/buildkit}"
+CACHE_DIR="${SM121_BUILDKIT_CACHE:-$HOME/.cache/sm121-buildkit}"
 mkdir -p "${CACHE_DIR}"
 docker buildx build --cache-from "type=local,src=${CACHE_DIR}" \
     --cache-to "type=local,dest=${CACHE_DIR},mode=max" \

+ 1 - 1
docker-images/glm53-flash-dflash2/build.sh

@@ -24,7 +24,7 @@ echo "=== Building ${TAG} ==="
 # version-independent stages (apt, torch, CUDA base) are reused across
 # rebuilds — only the pinned source stages (vllm/b12x/…) re-download and
 # re-compile. Override the dir with SM121_BUILDKIT_CACHE if desired.
-CACHE_DIR="${SM121_BUILDKIT_CACHE:-$HOME/sm121-cache/buildkit}"
+CACHE_DIR="${SM121_BUILDKIT_CACHE:-$HOME/.cache/sm121-buildkit}"
 mkdir -p "${CACHE_DIR}"
 docker buildx build "${ARGS[@]}" --cache-from "type=local,src=${CACHE_DIR}" \
     --cache-to "type=local,dest=${CACHE_DIR},mode=max" \

+ 1 - 1
docker-images/glm53-flash-exl3/build.sh

@@ -24,7 +24,7 @@ echo "=== Building ${TAG} ==="
 # version-independent stages (apt, torch, CUDA base) are reused across
 # rebuilds — only the pinned source stages (vllm/b12x/…) re-download and
 # re-compile. Override the dir with SM121_BUILDKIT_CACHE if desired.
-CACHE_DIR="${SM121_BUILDKIT_CACHE:-$HOME/sm121-cache/buildkit}"
+CACHE_DIR="${SM121_BUILDKIT_CACHE:-$HOME/.cache/sm121-buildkit}"
 mkdir -p "${CACHE_DIR}"
 docker buildx build "${ARGS[@]}" --cache-from "type=local,src=${CACHE_DIR}" \
     --cache-to "type=local,dest=${CACHE_DIR},mode=max" \

+ 1 - 1
docker-images/glm53-flash-nvfp4-4p67/build.sh

@@ -24,7 +24,7 @@ echo "=== Building ${TAG} ==="
 # version-independent stages (apt, torch, CUDA base) are reused across
 # rebuilds — only the pinned source stages (vllm/b12x/…) re-download and
 # re-compile. Override the dir with SM121_BUILDKIT_CACHE if desired.
-CACHE_DIR="${SM121_BUILDKIT_CACHE:-$HOME/sm121-cache/buildkit}"
+CACHE_DIR="${SM121_BUILDKIT_CACHE:-$HOME/.cache/sm121-buildkit}"
 mkdir -p "${CACHE_DIR}"
 docker buildx build "${ARGS[@]}" --cache-from "type=local,src=${CACHE_DIR}" \
     --cache-to "type=local,dest=${CACHE_DIR},mode=max" \

+ 1 - 1
docker-images/glm53-flash-nvfp4-lab/build.sh

@@ -24,7 +24,7 @@ echo "=== Building ${TAG} ==="
 # version-independent stages (apt, torch, CUDA base) are reused across
 # rebuilds — only the pinned source stages (vllm/b12x/…) re-download and
 # re-compile. Override the dir with SM121_BUILDKIT_CACHE if desired.
-CACHE_DIR="${SM121_BUILDKIT_CACHE:-$HOME/sm121-cache/buildkit}"
+CACHE_DIR="${SM121_BUILDKIT_CACHE:-$HOME/.cache/sm121-buildkit}"
 mkdir -p "${CACHE_DIR}"
 docker buildx build "${ARGS[@]}" --cache-from "type=local,src=${CACHE_DIR}" \
     --cache-to "type=local,dest=${CACHE_DIR},mode=max" \

+ 1 - 1
docker-images/glm53-flash-nvfp4/build.sh

@@ -22,7 +22,7 @@ echo "=== Building ${TAG} ==="
 # version-independent stages (apt, torch, CUDA base) are reused across
 # rebuilds — only the pinned source stages (vllm/b12x/…) re-download and
 # re-compile. Override the dir with SM121_BUILDKIT_CACHE if desired.
-CACHE_DIR="${SM121_BUILDKIT_CACHE:-$HOME/sm121-cache/buildkit}"
+CACHE_DIR="${SM121_BUILDKIT_CACHE:-$HOME/.cache/sm121-buildkit}"
 mkdir -p "${CACHE_DIR}"
 docker buildx build "${ARGS[@]}" --cache-from "type=local,src=${CACHE_DIR}" \
     --cache-to "type=local,dest=${CACHE_DIR},mode=max" \

+ 1 - 1
docker-images/qwen38-flashnext-nvfp4/build.sh

@@ -22,7 +22,7 @@ echo "=== Building ${TAG} ==="
 # version-independent stages (apt, torch, CUDA base) are reused across
 # rebuilds — only the pinned source stages (vllm/b12x/…) re-download and
 # re-compile. Override the dir with SM121_BUILDKIT_CACHE if desired.
-CACHE_DIR="${SM121_BUILDKIT_CACHE:-$HOME/sm121-cache/buildkit}"
+CACHE_DIR="${SM121_BUILDKIT_CACHE:-$HOME/.cache/sm121-buildkit}"
 mkdir -p "${CACHE_DIR}"
 docker buildx build "${ARGS[@]}" --cache-from "type=local,src=${CACHE_DIR}" \
     --cache-to "type=local,dest=${CACHE_DIR},mode=max" \

+ 1 - 1
docker-images/solar-open2-nvfp4/build.sh

@@ -26,7 +26,7 @@ echo "=== Building ${TAG} ==="
 # version-independent stages (apt, torch, CUDA base) are reused across
 # rebuilds — only the pinned source stages (vllm/b12x/…) re-download and
 # re-compile. Override the dir with SM121_BUILDKIT_CACHE if desired.
-CACHE_DIR="${SM121_BUILDKIT_CACHE:-$HOME/sm121-cache/buildkit}"
+CACHE_DIR="${SM121_BUILDKIT_CACHE:-$HOME/.cache/sm121-buildkit}"
 mkdir -p "${CACHE_DIR}"
 docker buildx build "${ARGS[@]}" --cache-from "type=local,src=${CACHE_DIR}" \
     --cache-to "type=local,dest=${CACHE_DIR},mode=max" \

+ 0 - 0
recipes/glm53-flash-r28-4x-dcp4/reports/2026-09-08_182306_r28-4x-dcp4.log


+ 0 - 0
recipes/glm53-flash-r28-4x-dcp4/reports/2026-09-08_182734_r28-4x-dcp4.log


+ 0 - 0
recipes/glm53-flash-r28-4x-dcp4/reports/2026-09-08_183637_r28-4x-dcp4.log


+ 0 - 0
recipes/glm53-flash-r28-4x-dcp4/reports/2026-09-08_183747_dbg.log


+ 0 - 0
recipes/glm53-flash-r28-4x-dcp4/reports/2026-09-08_184030_dbg2.log


+ 0 - 0
recipes/glm53-flash-r28-4x-dcp4/reports/2026-09-08_184035_dbg3.log


+ 0 - 0
recipes/glm53-flash-r28-4x-dcp4/reports/2026-09-08_184116_dbg4.log


+ 0 - 0
recipes/glm53-flash-r28-4x-dcp4/reports/2026-09-08_184347_dbg5.log


+ 0 - 0
recipes/glm53-flash-r28-4x-dcp4/reports/2026-09-08_184442_dbg6.log


+ 44 - 3
src/bench.rs

@@ -137,10 +137,20 @@ async fn wait_ready(base_url: &str) -> Result<()> {
 }
 
 /// Tee one output pipe to the console and the log file.
+/// Tee one output pipe to the console and the log file.
+///
+/// The read buffer must not be empty: `read()` on a zero-length slice
+/// returns 0 (interpreted as EOF), so an empty `Vec` would silently sever
+/// the pipe — the child then dies on its first stderr write with EPIPE
+/// (silent exit 120 in CPython).
 fn pump<R: Read>(mut reader: R, to_console: bool, file: Arc<Mutex<fs::File>>) {
-    let mut buf = Vec::new();
+    let mut buf = vec![0u8; 8192];
     loop {
-        let n = reader.read(&mut buf).expect("reading bench output pipe");
+        let n = match reader.read(&mut buf) {
+            Ok(n) => n,
+            Err(e) if e.kind() == std::io::ErrorKind::Interrupted => continue,
+            Err(e) => panic!("reading bench output pipe: {e}"),
+        };
         if n == 0 {
             break;
         }
@@ -150,7 +160,6 @@ fn pump<R: Read>(mut reader: R, to_console: bool, file: Arc<Mutex<fs::File>>) {
         if let Ok(mut f) = file.lock() {
             f.write_all(&buf[..n]).ok();
         }
-        buf.clear();
     }
 }
 
@@ -321,6 +330,38 @@ mod tests {
         assert_eq!(civil_date(20_818), (2026, 12, 31));
     }
 
+    /// Regression: the pump must not treat a not-yet-written pipe as EOF.
+    /// `read()` into a zero-length buffer returns 0, which the old pump
+    /// treated as EOF — closing the pipe and killing the child on its first
+    /// stderr write with EPIPE (silent exit 120 in CPython).
+    #[test]
+    fn pump_keeps_pipe_open_until_child_exits() {
+        let tmp =
+            std::env::temp_dir().join(format!("sm121-bench-pump-test-{}", std::process::id()));
+        fs::create_dir_all(&tmp).unwrap();
+        let log = tmp.join("pump.log");
+
+        let mut child = Command::new("sh")
+            .args(["-c", "sleep 0.3; echo late-output >&2"])
+            .stdout(Stdio::piped())
+            .stderr(Stdio::piped())
+            .spawn()
+            .unwrap();
+        let out = child.stdout.take().unwrap();
+        let err = child.stderr.take().unwrap();
+        let f = Arc::new(Mutex::new(fs::File::create(&log).unwrap()));
+        let f1 = f.clone();
+        let t1 = std::thread::spawn(move || pump(out, false, f1));
+        let t2 = std::thread::spawn(move || pump(err, false, f));
+        let status = child.wait().unwrap();
+        t1.join().unwrap();
+        t2.join().unwrap();
+        let body = fs::read_to_string(&log).unwrap();
+        assert_eq!(status.code(), Some(0), "child must not hit EPIPE: {body}");
+        assert!(body.contains("late-output"), "piped stderr lost: {body:?}");
+        let _ = fs::remove_dir_all(&tmp);
+    }
+
     #[test]
     fn sanitize_replaces_bad_chars() {
         assert_eq!(sanitize("arm B: 2x"), "arm-B--2x");