Skip to content

Bench/zstd integration - #68

Open
pyq623 wants to merge 3 commits into
one-covenant:mainfrom
pyq623:bench/zstd-integration
Open

Bench/zstd integration#68
pyq623 wants to merge 3 commits into
one-covenant:mainfrom
pyq623:bench/zstd-integration

Conversation

@pyq623

@pyq623 pyq623 commented Jul 19, 2026

Copy link
Copy Markdown

Summary by CodeRabbit

  • New Features

    • Added benchmarking and acceptance workflows for checkpoint publishing, sparse deltas, disk synchronization, NCCL transport, SGLang reloads, and S3-compatible transfers.
    • Added preflight and smoke checks for delta checkpoint integrity, communications, storage, and mocked services.
    • Added tools to summarize synchronization metrics and benchmark results in human-readable and JSON formats.
    • Added reusable local object-storage test support and chain simulation.
  • Bug Fixes

    • Made S3 read timeouts configurable.
    • Added timing metrics for server startup and weight synchronization.

pengzai.pyq and others added 2 commits July 10, 2026 00:26
… sync

Emit grep-able per-stage timing (server_start_s, update_weights_from_disk_s)
to stdout plus fire-and-forget monitoring gauges. Fully guarded so
measurement can never break the sync path. Bench context: docs/goal.md
Phase 2 S4 (miner-side weight sync latency measurement).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Completing a 57 GB multipart upload against a slow local S3 mock (moto,
single-threaded in-memory assembly) exceeds the hardcoded 30 s read
timeout. Default unchanged. Bench context: docs/goal.md Phase 2 S7.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@pyq623, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 35 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5516525b-a056-4133-9a07-259814ac0f3c

📥 Commits

Reviewing files that changed from the base of the PR and between 61b3a52 and 8b8436a.

📒 Files selected for processing (28)
  • docs/sbatch/bench_qwen3_30b_grail_delta.sbatch
  • docs/sbatch/preflight_grail.sbatch
  • docs/sbatch/s13_preflight.sbatch
  • docs/sbatch/s3_miner_smoke.sbatch
  • docs/sbatch/s4_sync_bench.sbatch
  • docs/sbatch/s5_sglang_30b_reload.sbatch
  • docs/sbatch/s6_disk_segments.sbatch
  • docs/sbatch/s7_delta_e2e.sbatch
  • docs/sbatch/s8_nccl.sbatch
  • docs/scripts/bench_qwen3_30b_grail_delta.py
  • docs/scripts/grail_chain_mock.py
  • docs/scripts/moto_helper.sh
  • docs/scripts/parse_sync_metrics.py
  • docs/scripts/probe_grail.py
  • docs/scripts/s0_comms_roundtrip.py
  • docs/scripts/s13_preflight_moto.py
  • docs/scripts/s13_transfer_bench.py
  • docs/scripts/s1_publish_consume.py
  • docs/scripts/s22_multistep_delta_bench.py
  • docs/scripts/s2_run_miner_mocked.py
  • docs/scripts/s4_rotate_publisher.py
  • docs/scripts/s5_sglang_reload_bench.py
  • docs/scripts/s6_disk_segments_bench.py
  • docs/scripts/s7_rotate_delta_publisher.py
  • docs/scripts/s8_nccl_bench.py
  • docs/scripts/s8_nccl_bench_2n.py
  • docs/scripts/s8_nccl_bench_fsdp.py
  • docs/scripts/s9_real_delta_bench.py

Walkthrough

The PR adds benchmark scripts and Slurm jobs covering sparse delta checkpoints, mocked S3 throughput, checkpoint-chain smoke tests, NCCL synchronization, disk-backed transfers, and distributed REAL-training delta workflows. It also adds synchronization metrics and configurable S3 read timeouts.

Changes

Benchmark and checkpoint synchronization suite

Layer / File(s) Summary
Local delta and disk benchmarks
docs/sbatch/bench_qwen3_30b_grail_delta.sbatch, docs/sbatch/preflight_grail.sbatch, docs/sbatch/s5_sglang_30b_reload.sbatch, docs/scripts/bench_qwen3_30b_grail_delta.py, docs/scripts/probe_grail.py, docs/scripts/s5_sglang_reload_bench.py, docs/scripts/s6_disk_segments_bench.py
Adds sparse-delta, preflight, SGLang reload, and per-segment disk benchmark workflows with timing, cache, integrity, and JSON reporting.
Mocked S3 throughput workflows
docs/sbatch/s13_preflight.sbatch, docs/scripts/moto_helper.sh, docs/scripts/s0_comms_roundtrip.py, docs/scripts/s13_preflight_moto.py, docs/scripts/s13_transfer_bench.py
Adds moto lifecycle management, S3 round-trip validation, throughput measurements, memory sampling, and steady-state reports.
Checkpoint chain and miner smoke flows
docs/sbatch/s3_miner_smoke.sbatch, docs/sbatch/s4_sync_bench.sbatch, docs/sbatch/s7_delta_e2e.sbatch, docs/scripts/grail_chain_mock.py, docs/scripts/s1_publish_consume.py, docs/scripts/s2_run_miner_mocked.py, docs/scripts/s4_rotate_publisher.py, docs/scripts/s7_rotate_delta_publisher.py, docs/scripts/parse_sync_metrics.py, grail/mining/weight_sync.py, grail/infrastructure/comms.py
Adds mocked chain integration, full and delta checkpoint publishing, miner execution, end-to-end acceptance checks, log parsing, synchronization metrics, and configurable S3 read timeouts.
NCCL delta synchronization benchmarks
docs/sbatch/s8_nccl.sbatch, docs/scripts/s8_nccl_bench.py, docs/scripts/s8_nccl_bench_2n.py, docs/scripts/s8_nccl_bench_fsdp.py
Adds dense and sparse NCCL transport modes, disk-backed delta delivery, FSDP trainer paths, miner application, hash verification, rollback handling, and aggregated metrics.
REAL-training delta benchmarks
docs/scripts/s22_multistep_delta_bench.py, docs/scripts/s9_real_delta_bench.py
Adds distributed optimizer-step training, FSDP model handling, multistep sparse delta measurement, disk or NCCL transport, miner verification, and steady-state reports.

Estimated code review effort: 5 (Critical) | ~120 minutes

Poem

I’m a rabbit with benchmarks to run,
Through sparse little deltas I hop in the sun.
Moto bubbles, NCCL beams,
Checkpoints flow through measured streams.
With hashes bright and logs in sight,
The burrow syncs its weights just right.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.48% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and relevant; it captures the benchmark/zstd integration theme of the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🧹 Nitpick comments (3)
docs/scripts/s9_real_delta_bench.py (1)

122-122: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Hoist stdlib inline imports to the module top. Both scripts import standard-library modules inside functions/branches; move the movable ones to the top-level import block (stdlib group). The sparse_codec import via sys.path is intentionally deferred and can stay inline.

  • docs/scripts/s9_real_delta_bench.py#L122-L122: move import functools (and pandas at L230, threading at L398/L467) to the top.
  • docs/scripts/s22_multistep_delta_bench.py#L120-L120: move import functools (and json as _json at L185, pandas at L228, deque at L275) to the top.

As per coding guidelines: "Place imports at the top of Python files; order: stdlib, third-party, local; avoid inline imports".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/scripts/s9_real_delta_bench.py` at line 122, Move the movable inline
imports to each script’s module-level import block, preserving
stdlib-before-third-party ordering: in docs/scripts/s9_real_delta_bench.py move
functools, threading, and pandas; in docs/scripts/s22_multistep_delta_bench.py
move functools, json as _json, deque, and pandas. Leave the deferred
sparse_codec sys.path import inline.

Source: Coding guidelines

docs/scripts/s4_rotate_publisher.py (1)

45-47: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

current_window() formula duplicated verbatim across both rotator scripts. The window-boundary calculation ((block // WINDOW_LEN) * WINDOW_LEN) is copy-pasted identically in two files; since correctness of the whole delta/full rotation flow depends on rotators and the mocked miner agreeing on window boundaries, any future tweak to this formula (or to WINDOW_LEN) risks silently drifting between the two files if only one is updated.

  • docs/scripts/s4_rotate_publisher.py#L45-L47: extract current_window() (and the shared WINDOW_LEN/GENESIS/BLOCK_TIME constants) into a small shared bench-utils module imported by both rotators.
  • docs/scripts/s7_rotate_delta_publisher.py#L57-L58: import the same shared helper instead of redefining it.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/scripts/s4_rotate_publisher.py` around lines 45 - 47, The current_window
formula and its WINDOW_LEN, GENESIS, and BLOCK_TIME constants are duplicated
across both rotator scripts. In docs/scripts/s4_rotate_publisher.py lines 45-47,
extract these shared constants and current_window() into a small bench-utils
module; in docs/scripts/s7_rotate_delta_publisher.py lines 57-58, remove the
duplicate definitions and import the shared helper so both rotators use the same
window-boundary implementation.
docs/scripts/s2_run_miner_mocked.py (1)

27-28: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Hardcoded per-user absolute path duplicated across 5 scripts. Every entry point independently hardcodes sys.path.insert(0, "/storage/openpsi/users/pengzai.pyq/grail") instead of reading the GRAIL_SRC/WORKDIR value the sbatch wrappers already compute; some wrappers already export PYTHONPATH from that same variable, making the Python-side hardcode both redundant and a source of stale imports if the path ever changes or another user runs these scripts.

  • docs/scripts/s2_run_miner_mocked.py#L27-L28: derive the path from an env var (e.g. GRAIL_SRC) instead of the literal string.
  • docs/scripts/s4_rotate_publisher.py#L21-L21: same change.
  • docs/scripts/s5_sglang_reload_bench.py#L27-L27: same change.
  • docs/scripts/s6_disk_segments_bench.py#L29-L29: same change.
  • docs/scripts/s7_rotate_delta_publisher.py#L23-L23: same change.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/scripts/s2_run_miner_mocked.py` around lines 27 - 28, Replace the
hardcoded user-specific source paths with an environment-derived path such as
GRAIL_SRC, preserving each script’s existing sys.path setup. Apply this change
in docs/scripts/s2_run_miner_mocked.py lines 27-28,
docs/scripts/s4_rotate_publisher.py line 21,
docs/scripts/s5_sglang_reload_bench.py line 27,
docs/scripts/s6_disk_segments_bench.py line 29, and
docs/scripts/s7_rotate_delta_publisher.py line 23, using the sbatch-provided
environment value so all entry points resolve the current checkout consistently.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/sbatch/s13_preflight.sbatch`:
- Around line 39-50: Update the singularity execution flow before the RC
assignment so its non-zero status is captured without triggering set -e, while
preserving the command and its arguments. Ensure the subsequent completion
message reports the captured status and the script exits with that same status.

In `@docs/scripts/probe_grail.py`:
- Around line 24-29: Move the local imports from lines 24-29, the third-party
imports from lines 34-38, and the third-party import from line 127 in
docs/scripts/probe_grail.py to the file’s import section, ordered as
standard-library, third-party, then local modules; remove the original inline
import statements while preserving existing usage.
- Around line 44-55: Refactor docs/scripts/probe_grail.py by replacing all print
calls with a module logger obtained via logging.getLogger(__name__), adding
explicit type annotations for variables and relevant function values to satisfy
mypy-strict, and moving module-level execution into single-responsibility
functions of roughly 5–20 lines. Preserve the existing probe behavior,
assertions, and output information while keeping the script entry point minimal.
- Line 21: Replace the hardcoded configuration values in
docs/scripts/probe_grail.py at lines 21-21, 129-131, and 135-135: derive the
grail source path from __file__ or load it from an environment variable, load
the target model path from an environment variable, and load the storage disk
path from an environment variable instead of /storage. Use the existing
probe_grail configuration flow and provide appropriate handling for unset
variables.

In `@docs/scripts/s13_transfer_bench.py`:
- Around line 225-227: Guard the directory-creation step before writing the
output in the transfer benchmark flow: only call os.makedirs when
os.path.dirname(args.out) is non-empty, while preserving the existing
file-writing behavior for both bare filenames and paths with directories.

In `@docs/scripts/s2_run_miner_mocked.py`:
- Around line 46-49: Update the task-waiting error handling around
asyncio.wait_for in the harness so asyncio.TimeoutError is handled separately,
while other exceptions are logged with shutdown context and re-raised. Preserve
task cancellation on timeout and ensure genuine miner failures are no longer
silently swallowed.

In `@docs/scripts/s5_sglang_reload_bench.py`:
- Around line 78-113: Wrap the server lifecycle and benchmark request loop in a
try/finally so manager.__aexit__(None, None, None) always runs after
manager.__aenter__() succeeds, including when start_server(), client.post(), or
resp.raise_for_status() raises. Preserve the existing startup, measurement, and
result-recording behavior while guaranteeing SGLang process cleanup.

In `@docs/scripts/s6_disk_segments_bench.py`:
- Around line 113-116: Validate the result of load_model_state_dict(MODEL)
before the `for k in state` loop, and raise a clear error when it is None or
otherwise indicates missing weights. Preserve the existing cloning and
RAM-materialization behavior for valid state dictionaries.

---

Nitpick comments:
In `@docs/scripts/s2_run_miner_mocked.py`:
- Around line 27-28: Replace the hardcoded user-specific source paths with an
environment-derived path such as GRAIL_SRC, preserving each script’s existing
sys.path setup. Apply this change in docs/scripts/s2_run_miner_mocked.py lines
27-28, docs/scripts/s4_rotate_publisher.py line 21,
docs/scripts/s5_sglang_reload_bench.py line 27,
docs/scripts/s6_disk_segments_bench.py line 29, and
docs/scripts/s7_rotate_delta_publisher.py line 23, using the sbatch-provided
environment value so all entry points resolve the current checkout consistently.

In `@docs/scripts/s4_rotate_publisher.py`:
- Around line 45-47: The current_window formula and its WINDOW_LEN, GENESIS, and
BLOCK_TIME constants are duplicated across both rotator scripts. In
docs/scripts/s4_rotate_publisher.py lines 45-47, extract these shared constants
and current_window() into a small bench-utils module; in
docs/scripts/s7_rotate_delta_publisher.py lines 57-58, remove the duplicate
definitions and import the shared helper so both rotators use the same
window-boundary implementation.

In `@docs/scripts/s9_real_delta_bench.py`:
- Line 122: Move the movable inline imports to each script’s module-level import
block, preserving stdlib-before-third-party ordering: in
docs/scripts/s9_real_delta_bench.py move functools, threading, and pandas; in
docs/scripts/s22_multistep_delta_bench.py move functools, json as _json, deque,
and pandas. Leave the deferred sparse_codec sys.path import inline.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3e15c7cb-c816-4680-a7c9-74fb5a308a4c

📥 Commits

Reviewing files that changed from the base of the PR and between 54d6342 and 61b3a52.

📒 Files selected for processing (30)
  • docs/sbatch/bench_qwen3_30b_grail_delta.sbatch
  • docs/sbatch/preflight_grail.sbatch
  • docs/sbatch/s13_preflight.sbatch
  • docs/sbatch/s3_miner_smoke.sbatch
  • docs/sbatch/s4_sync_bench.sbatch
  • docs/sbatch/s5_sglang_30b_reload.sbatch
  • docs/sbatch/s6_disk_segments.sbatch
  • docs/sbatch/s7_delta_e2e.sbatch
  • docs/sbatch/s8_nccl.sbatch
  • docs/scripts/bench_qwen3_30b_grail_delta.py
  • docs/scripts/grail_chain_mock.py
  • docs/scripts/moto_helper.sh
  • docs/scripts/parse_sync_metrics.py
  • docs/scripts/probe_grail.py
  • docs/scripts/s0_comms_roundtrip.py
  • docs/scripts/s13_preflight_moto.py
  • docs/scripts/s13_transfer_bench.py
  • docs/scripts/s1_publish_consume.py
  • docs/scripts/s22_multistep_delta_bench.py
  • docs/scripts/s2_run_miner_mocked.py
  • docs/scripts/s4_rotate_publisher.py
  • docs/scripts/s5_sglang_reload_bench.py
  • docs/scripts/s6_disk_segments_bench.py
  • docs/scripts/s7_rotate_delta_publisher.py
  • docs/scripts/s8_nccl_bench.py
  • docs/scripts/s8_nccl_bench_2n.py
  • docs/scripts/s8_nccl_bench_fsdp.py
  • docs/scripts/s9_real_delta_bench.py
  • grail/infrastructure/comms.py
  • grail/mining/weight_sync.py

Comment on lines +39 to +50
singularity exec --no-home -B /storage:/storage \
--env PYTHONPATH=$WORKDIR/tools/moto_env312 \
"$SIF" /app/.venv/bin/python \
"$GRAIL_SRC/docs/scripts/s13_preflight_moto.py" \
--port "$PORT" \
--sizes 0.5,1,3 \
--rounds 3 \
--out "$JSON_OUT"
RC=$?

echo "===== S13 preflight done rc=$RC JSON=$JSON_OUT ====="
exit $RC

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

RC=$? is unreachable on failure under set -e.

With errexit enabled (Line 17), a non-zero exit from the singularity exec block terminates the script immediately (firing the EXIT trap), so Line 47 only runs on success and RC is always 0. The rc=$RC report never reflects a real failure.

🐛 Capture rc without tripping errexit
-singularity exec --no-home -B /storage:/storage \
+RC=0
+singularity exec --no-home -B /storage:/storage \
   --env PYTHONPATH=$WORKDIR/tools/moto_env312 \
   "$SIF" /app/.venv/bin/python \
     "$GRAIL_SRC/docs/scripts/s13_preflight_moto.py" \
     --port "$PORT" \
     --sizes 0.5,1,3 \
     --rounds 3 \
-    --out "$JSON_OUT"
-RC=$?
+    --out "$JSON_OUT" || RC=$?
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
singularity exec --no-home -B /storage:/storage \
--env PYTHONPATH=$WORKDIR/tools/moto_env312 \
"$SIF" /app/.venv/bin/python \
"$GRAIL_SRC/docs/scripts/s13_preflight_moto.py" \
--port "$PORT" \
--sizes 0.5,1,3 \
--rounds 3 \
--out "$JSON_OUT"
RC=$?
echo "===== S13 preflight done rc=$RC JSON=$JSON_OUT ====="
exit $RC
RC=0
singularity exec --no-home -B /storage:/storage \
--env PYTHONPATH=$WORKDIR/tools/moto_env312 \
"$SIF" /app/.venv/bin/python \
"$GRAIL_SRC/docs/scripts/s13_preflight_moto.py" \
--port "$PORT" \
--sizes 0.5,1,3 \
--rounds 3 \
--out "$JSON_OUT" || RC=$?
echo "===== S13 preflight done rc=$RC JSON=$JSON_OUT ====="
exit $RC
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/sbatch/s13_preflight.sbatch` around lines 39 - 50, Update the
singularity execution flow before the RC assignment so its non-zero status is
captured without triggering set -e, while preserving the command and its
arguments. Ensure the subsequent completion message reports the captured status
and the script exits with that same status.

import sys
import time

sys.path.insert(0, "/storage/openpsi/users/pengzai.pyq/grail")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Load configuration via environment variables.

As per coding guidelines, configuration and cluster-specific values must be loaded via environment variables (e.g., using dotenv or os.environ.get) rather than being hardcoded.

  • docs/scripts/probe_grail.py#L21-L21: Load the grail source path using an environment variable or a relative path derived from __file__.
  • docs/scripts/probe_grail.py#L129-L131: Load the target model path using an environment variable instead of hardcoding it.
  • docs/scripts/probe_grail.py#L135-L135: Load the storage disk path using an environment variable instead of hardcoding /storage.
📍 Affects 1 file
  • docs/scripts/probe_grail.py#L21-L21 (this comment)
  • docs/scripts/probe_grail.py#L129-L131
  • docs/scripts/probe_grail.py#L135-L135
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/scripts/probe_grail.py` at line 21, Replace the hardcoded configuration
values in docs/scripts/probe_grail.py at lines 21-21, 129-131, and 135-135:
derive the grail source path from __file__ or load it from an environment
variable, load the target model path from an environment variable, and load the
storage disk path from an environment variable instead of /storage. Use the
existing probe_grail configuration flow and provide appropriate handling for
unset variables.

Source: Coding guidelines

Comment on lines +24 to +29
from grail.infrastructure.delta_checkpoint import ( # noqa: E402
apply_sparse_delta,
compute_sparse_delta,
compute_weights_hash,
estimate_sparse_size,
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Move all imports to the top of the file.

As per coding guidelines, avoid inline imports and place all imports at the top of the file, explicitly ordered by standard library, third-party, and local modules.

  • docs/scripts/probe_grail.py#L24-L29: Move these local imports to the top.
  • docs/scripts/probe_grail.py#L34-L38: Move these third-party library imports to the top.
  • docs/scripts/probe_grail.py#L127-L127: Move this third-party library import to the top.
📍 Affects 1 file
  • docs/scripts/probe_grail.py#L24-L29 (this comment)
  • docs/scripts/probe_grail.py#L34-L38
  • docs/scripts/probe_grail.py#L127-L127
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/scripts/probe_grail.py` around lines 24 - 29, Move the local imports
from lines 24-29, the third-party imports from lines 34-38, and the third-party
import from line 127 in docs/scripts/probe_grail.py to the file’s import
section, ordered as standard-library, third-party, then local modules; remove
the original inline import statements while preserving existing usage.

Source: Coding guidelines

Comment on lines +44 to +55
print("\n=== [3/6] tiny synthetic round-trip ===")
g = torch.Generator().manual_seed(1234)
base = {"w1": torch.randn(100, 200, generator=g), "w2": torch.randn(50, 50, generator=g)}
current = {k: v.clone() for k, v in base.items()}
current["w1"].view(-1)[torch.randperm(100 * 200, generator=g)[:100]] = 0.5
sparse, shapes, stats = compute_sparse_delta(current, base)
print(f"stats: total={stats['total_params']} changed={stats['nonzero_params']}")
assert stats["nonzero_params"] == 100, f"expected 100 changed, got {stats['nonzero_params']}"
reconstructed = apply_sparse_delta(base, sparse, shapes)
ok = all(torch.equal(current[k].cpu(), reconstructed[k].cpu()) for k in current)
print("tiny reconstruct OK:", ok)
assert ok

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

Refactor script for logging, type hints, and function encapsulation.

As per coding guidelines, please update the script to adhere to the project's structural standards:

  • Replace print statements with logging.getLogger(__name__).
  • Add full type hints to all variables for mypy-strict compatibility.
  • Encapsulate the module-level execution logic into single-responsibility functions (target 5–20 lines).

Apply these changes throughout the file.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/scripts/probe_grail.py` around lines 44 - 55, Refactor
docs/scripts/probe_grail.py by replacing all print calls with a module logger
obtained via logging.getLogger(__name__), adding explicit type annotations for
variables and relevant function values to satisfy mypy-strict, and moving
module-level execution into single-responsibility functions of roughly 5–20
lines. Preserve the existing probe behavior, assertions, and output information
while keeping the script entry point minimal.

Source: Coding guidelines

Comment on lines +225 to +227
os.makedirs(os.path.dirname(args.out), exist_ok=True)
with open(args.out, "w") as f:
json.dump(out_obj, f, indent=2)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

os.makedirs crashes on a bare-filename --out.

If --out is a filename with no directory component, os.path.dirname(args.out) returns "" and os.makedirs("", exist_ok=True) raises FileNotFoundError, discarding results after the full run completes. Guard the empty case.

🐛 Guard empty dirname
-    os.makedirs(os.path.dirname(args.out), exist_ok=True)
+    out_dir = os.path.dirname(args.out)
+    if out_dir:
+        os.makedirs(out_dir, exist_ok=True)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
os.makedirs(os.path.dirname(args.out), exist_ok=True)
with open(args.out, "w") as f:
json.dump(out_obj, f, indent=2)
out_dir = os.path.dirname(args.out)
if out_dir:
os.makedirs(out_dir, exist_ok=True)
with open(args.out, "w") as f:
json.dump(out_obj, f, indent=2)
🧰 Tools
🪛 ast-grep (0.44.1)

[info] 227-227: use jsonify instead of json.dumps for JSON output
Context: json.dumps(steady, indent=2)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[warning] 225-225: File path is request-/variable-derived; validate and normalize to prevent path traversal.
Context: open(args.out, "w")
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(open-filename-from-request)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/scripts/s13_transfer_bench.py` around lines 225 - 227, Guard the
directory-creation step before writing the output in the transfer benchmark
flow: only call os.makedirs when os.path.dirname(args.out) is non-empty, while
preserving the existing file-writing behavior for both bare filenames and paths
with directories.

Comment on lines +46 to +49
try:
await asyncio.wait_for(task, timeout=30)
except (asyncio.TimeoutError, Exception):
task.cancel()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Broad except silently discards real shutdown errors.

except (asyncio.TimeoutError, Exception): catches everything (the TimeoutError in the tuple is redundant) and swallows the exception without logging it. Since this harness exists specifically to catch miner crashes, a genuine error here disappears silently.
As per coding guidelines, "No silent failures; log with context and re-raise when you can't handle errors."

🐛 Proposed fix
+        import logging as _logging
         try:
             await asyncio.wait_for(task, timeout=30)
-        except (asyncio.TimeoutError, Exception):
+        except asyncio.TimeoutError:
+            task.cancel()
+        except Exception:
+            _logging.getLogger(__name__).exception("Miner task raised during shutdown")
             task.cancel()
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try:
await asyncio.wait_for(task, timeout=30)
except (asyncio.TimeoutError, Exception):
task.cancel()
import logging as _logging
try:
await asyncio.wait_for(task, timeout=30)
except asyncio.TimeoutError:
task.cancel()
except Exception:
_logging.getLogger(__name__).exception("Miner task raised during shutdown")
task.cancel()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/scripts/s2_run_miner_mocked.py` around lines 46 - 49, Update the
task-waiting error handling around asyncio.wait_for in the harness so
asyncio.TimeoutError is handled separately, while other exceptions are logged
with shutdown context and re-raised. Preserve task cancellation on timeout and
ensure genuine miner failures are no longer silently swallowed.

Source: Coding guidelines

Comment on lines +78 to +113
manager = SGLangServerManager(
config=ServerConfig(model_path=MODEL, timeout_s=1800.0, env=env),
eval_config=eval_config,
)

print(f"[s5] starting SGLang with {MODEL}", flush=True)
t0 = time.perf_counter()
await manager.__aenter__()
await manager.start_server()
t_start = time.perf_counter() - t0
print(f"[s5] server up in {t_start:.1f}s at {manager.base_url}", flush=True)

results: dict[str, Any] = {"model": MODEL, "server_start_s": t_start, "cold": [], "warm": []}

async with httpx.AsyncClient() as client:
for mode, iters in (("cold", COLD_ITERS), ("warm", WARM_ITERS)):
for i in range(iters):
if mode == "cold":
n_files, gb = drop_page_cache(MODEL)
print(f"[s5] fadvise dropped {n_files} shards ({gb:.1f} GB)", flush=True)
t0 = time.perf_counter()
resp = await client.post(
f"{manager.base_url}/update_weights_from_disk",
json={"model_path": MODEL},
timeout=1800.0,
)
resp.raise_for_status()
elapsed = time.perf_counter() - t0
results[mode].append(elapsed)
print(
f"[s5] reload {mode} #{i}: {elapsed:.2f}s "
f"(resp: {resp.json() if resp.headers.get('content-type', '').startswith('application/json') else resp.status_code})",
flush=True,
)

await manager.__aexit__(None, None, None)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Server/process leak if anything raises between start and __aexit__.

manager.__aenter__()/start_server() run with no try/finally guaranteeing manager.__aexit__(None, None, None) (line 113) executes. If client.post(...) inside the loop raises (timeout, HTTP error via raise_for_status()), the SGLang server process/GPU memory is orphaned — exactly the "escaped singularity children" failure class this PR documents hitting in production (see docs/sbatch/s7_delta_e2e.sbatch lines 42-49, "escaped processes from a previous run... SGLang held 126.9GB on GPU0").

🔒 Proposed fix: guarantee cleanup with try/finally
     print(f"[s5] starting SGLang with {MODEL}", flush=True)
     t0 = time.perf_counter()
     await manager.__aenter__()
-    await manager.start_server()
-    t_start = time.perf_counter() - t0
-    print(f"[s5] server up in {t_start:.1f}s at {manager.base_url}", flush=True)
-
-    results: dict[str, Any] = {"model": MODEL, "server_start_s": t_start, "cold": [], "warm": []}
-
-    async with httpx.AsyncClient() as client:
-        for mode, iters in (("cold", COLD_ITERS), ("warm", WARM_ITERS)):
-            ...
-
-    await manager.__aexit__(None, None, None)
+    try:
+        await manager.start_server()
+        t_start = time.perf_counter() - t0
+        print(f"[s5] server up in {t_start:.1f}s at {manager.base_url}", flush=True)
+
+        results: dict[str, Any] = {
+            "model": MODEL, "server_start_s": t_start, "cold": [], "warm": [],
+        }
+
+        async with httpx.AsyncClient() as client:
+            for mode, iters in (("cold", COLD_ITERS), ("warm", WARM_ITERS)):
+                ...
+    finally:
+        await manager.__aexit__(None, None, None)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
manager = SGLangServerManager(
config=ServerConfig(model_path=MODEL, timeout_s=1800.0, env=env),
eval_config=eval_config,
)
print(f"[s5] starting SGLang with {MODEL}", flush=True)
t0 = time.perf_counter()
await manager.__aenter__()
await manager.start_server()
t_start = time.perf_counter() - t0
print(f"[s5] server up in {t_start:.1f}s at {manager.base_url}", flush=True)
results: dict[str, Any] = {"model": MODEL, "server_start_s": t_start, "cold": [], "warm": []}
async with httpx.AsyncClient() as client:
for mode, iters in (("cold", COLD_ITERS), ("warm", WARM_ITERS)):
for i in range(iters):
if mode == "cold":
n_files, gb = drop_page_cache(MODEL)
print(f"[s5] fadvise dropped {n_files} shards ({gb:.1f} GB)", flush=True)
t0 = time.perf_counter()
resp = await client.post(
f"{manager.base_url}/update_weights_from_disk",
json={"model_path": MODEL},
timeout=1800.0,
)
resp.raise_for_status()
elapsed = time.perf_counter() - t0
results[mode].append(elapsed)
print(
f"[s5] reload {mode} #{i}: {elapsed:.2f}s "
f"(resp: {resp.json() if resp.headers.get('content-type', '').startswith('application/json') else resp.status_code})",
flush=True,
)
await manager.__aexit__(None, None, None)
manager = SGLangServerManager(
config=ServerConfig(model_path=MODEL, timeout_s=1800.0, env=env),
eval_config=eval_config,
)
print(f"[s5] starting SGLang with {MODEL}", flush=True)
t0 = time.perf_counter()
await manager.__aenter__()
try:
await manager.start_server()
t_start = time.perf_counter() - t0
print(f"[s5] server up in {t_start:.1f}s at {manager.base_url}", flush=True)
results: dict[str, Any] = {"model": MODEL, "server_start_s": t_start, "cold": [], "warm": []}
async with httpx.AsyncClient() as client:
for mode, iters in (("cold", COLD_ITERS), ("warm", WARM_ITERS)):
for i in range(iters):
if mode == "cold":
n_files, gb = drop_page_cache(MODEL)
print(f"[s5] fadvise dropped {n_files} shards ({gb:.1f} GB)", flush=True)
t0 = time.perf_counter()
resp = await client.post(
f"{manager.base_url}/update_weights_from_disk",
json={"model_path": MODEL},
timeout=1800.0,
)
resp.raise_for_status()
elapsed = time.perf_counter() - t0
results[mode].append(elapsed)
print(
f"[s5] reload {mode} #{i}: {elapsed:.2f}s "
f"(resp: {resp.json() if resp.headers.get('content-type', '').startswith('application/json') else resp.status_code})",
flush=True,
)
finally:
await manager.__aexit__(None, None, None)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/scripts/s5_sglang_reload_bench.py` around lines 78 - 113, Wrap the
server lifecycle and benchmark request loop in a try/finally so
manager.__aexit__(None, None, None) always runs after manager.__aenter__()
succeeds, including when start_server(), client.post(), or
resp.raise_for_status() raises. Preserve the existing startup, measurement, and
result-recording behavior while guaranteeing SGLang process cleanup.

Comment on lines +113 to +116
state = load_model_state_dict(MODEL)
for k in state:
state[k] = state[k].clone() # break mmap so staging write reads RAM, not CPFS
say("state dict materialized in RAM")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Add an explicit check for missing weights before iterating state.

load_model_state_dict can return None when no weights are found in MODEL; this iterates it unconditionally, so a misconfigured/missing model path fails with an opaque TypeError instead of a clear error.

🐛 Proposed fix
     state = load_model_state_dict(MODEL)
+    if state is None:
+        raise FileNotFoundError(f"No model weights found under {MODEL}")
     for k in state:
         state[k] = state[k].clone()  # break mmap so staging write reads RAM, not CPFS
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
state = load_model_state_dict(MODEL)
for k in state:
state[k] = state[k].clone() # break mmap so staging write reads RAM, not CPFS
say("state dict materialized in RAM")
state = load_model_state_dict(MODEL)
if state is None:
raise FileNotFoundError(f"No model weights found under {MODEL}")
for k in state:
state[k] = state[k].clone() # break mmap so staging write reads RAM, not CPFS
say("state dict materialized in RAM")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/scripts/s6_disk_segments_bench.py` around lines 113 - 116, Validate the
result of load_model_state_dict(MODEL) before the `for k in state` loop, and
raise a clear error when it is None or otherwise indicates missing weights.
Preserve the existing cloning and RAM-materialization behavior for valid state
dictionaries.

- s9_real_delta_bench: v2 pathway (gather to rank0 GPU) + fp32_shadow prealloc
  option (S9_PREALLOC_OPTIM), optional grail production sparse_codec (v2/v3
  zstd via S9_DISK_COMPRESS), miner unflatten for v2 flat-linear -> 2D COO,
  round-scoped memory peaks (reset each round for baseline delta measurement)
- s22 multistep delta bench: K=1..4 change ratio measurement per round
- s13 preflight + moto helper: local S3 mock (moto server) for pre-cluster tests
- s0..s8 comms/publish/consume/rotate benches: infra pathway smoke tests
- probe_grail / parse_sync_metrics: sync metric analysis helpers
- Slurm sbatch launchers for AIS + on-prem clusters (docs/sbatch/)

No changes to upstream tracked code. All additions live under docs/scripts/
and docs/sbatch/ and are opt-in bench/instrumentation.
@pyq623
pyq623 force-pushed the bench/zstd-integration branch from 61b3a52 to 8b8436a Compare July 19, 2026 07:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant