Skip to content

Add resumable skippy quantize workflows - #900

Closed
i386 wants to merge 125 commits into
mainfrom
feat/jianyang-skippy-quantize
Closed

Add resumable skippy quantize workflows#900
i386 wants to merge 125 commits into
mainfrom
feat/jianyang-skippy-quantize

Conversation

@i386

@i386 i386 commented Jun 23, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • add resumable skippy-quantize convert and quant job flows for split GGUF artifacts
  • add direct BF16/FP16 GGUF to quantized layer-package orchestration through skippy-model-package
  • require custom quant profiles to pass explicit tensor recipes instead of baked-in GLM/MTP quant modes

Validation

  • cargo test -p skippy-quantize
  • cargo clippy -p skippy-quantize --all-targets -- -D warnings

Summary by CodeRabbit

  • New Features
    • Added agent skills and workflow guides for resumable HuggingFace → GGUF conversion and GGUF quantization using windowed, manifest-driven progress.
    • Introduced skippy-quantize Rust CLI for native GGUF conversion/quantization with spooling, validation, and status reporting.
    • Added native MTP N1 verification via A/B runs, including remote stage orchestration and metrics output.
    • Added verify-span-local command for local inference/span verification reporting.
  • Documentation
    • Expanded the skill docs with end-to-end command examples, monitoring, and resume guidance.

@github-actions
github-actions Bot requested a review from ndizazzo June 23, 2026 21:04
@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: cdab18d2-5272-44dc-8f97-c97d6642f1c8

📥 Commits

Reviewing files that changed from the base of the PR and between a50b0a8 and 2ddd54f.

📒 Files selected for processing (2)
  • crates/skippy-quantize/src/gguf_template.rs
  • crates/skippy-quantize/src/main.rs
🚧 Files skipped from review as they are similar to previous changes (2)
  • crates/skippy-quantize/src/gguf_template.rs
  • crates/skippy-quantize/src/main.rs

📝 Walkthrough

Walkthrough

This PR adds skippy-quantize and llama-quant-ffi, removes direct-return TCP wiring from prompt/bench/runtime clients, and extends native MTP correctness and benchmark flows with ABI, report, and orchestration changes.

Changes

skippy-quantize and llama-quant-ffi: new quantization toolchain

Layer / File(s) Summary
llama-quant-ffi crate and linker setup
crates/llama-quant-ffi/...
Defines the crate manifest, quantization FFI types, dynamic loader, and build script for static/dynamic llama quant linkage.
workspace registration and build targets
Cargo.toml, crates/skippy-quantize/Cargo.toml, Justfile
Registers the new crates and adds build targets for debug, release, and standalone static-linked builds.
splits, manifest, and locking
crates/skippy-quantize/src/splits.rs, crates/skippy-quantize/src/manifest.rs, crates/skippy-quantize/src/locking.rs
Implements split discovery/progress, durable manifest persistence, and manifest locking.
HF checkpoint inspection, float conversion, metadata, and tensor mapping
crates/skippy-quantize/src/hf_checkpoint.rs, crates/skippy-quantize/src/float_convert.rs, crates/skippy-quantize/src/gguf_template.rs, crates/skippy-quantize/src/tensor_map.rs
Adds safetensors planning and streaming, float dtype conversion, HF config to GGUF metadata generation, and HF-to-GGUF tensor mapping.
GGUF writer and imatrix loader
crates/skippy-quantize/src/gguf_writer.rs, crates/skippy-quantize/src/gguf_writer_tests.rs, crates/skippy-quantize/src/imatrix.rs
Implements the SafeTensors→GGUF writer, split/expert handling, and legacy/GGUF imatrix loading.
backend probing, budgets, output, artifacts, and records
crates/skippy-quantize/src/backend.rs, crates/skippy-quantize/src/memory_budget.rs, crates/skippy-quantize/src/output.rs, crates/skippy-quantize/src/records.rs, crates/skippy-quantize/src/residency.rs, crates/skippy-quantize/src/artifacts.rs, crates/skippy-quantize/src/quantize.rs, crates/skippy-quantize/src/command_reports.rs
Adds backend probing, memory budgeting, JSON event reporting, window record persistence, filesystem residency helpers, spool publishing, and tensor-type validation.
native conversion, native quantization, preflight, and validation
crates/skippy-quantize/src/native_convert.rs, crates/skippy-quantize/src/native_quantize.rs, crates/skippy-quantize/src/plan_convert.rs, crates/skippy-quantize/src/preflight.rs, crates/skippy-quantize/src/llama_load.rs
Wires native conversion and quantization execution, conversion planning, preflight checks, and llama load validation.
skippy-quantize CLI, direct flows, and comparison script
crates/skippy-quantize/src/main.rs, crates/skippy-quantize/src/direct_convert.rs, crates/skippy-quantize/src/direct_quantize.rs, crates/skippy-quantize/scripts/compare-reference-quantization.py
Adds the main CLI, single-shot direct convert/quantize flows, and a Python parity comparison script.
skill docs, README, and reference-parity script
.agents/skills/hf-bf16-gguf-conversion-jobs/*, .agents/skills/hf-gguf-quant-jobs/*, .agents/skills/hf-layer-package-jobs/SKILL.md, .agents/skills/hf-quant-and-layer-package-jobs/*, crates/skippy-quantize/README.md
Adds and updates workflow docs, command templates, monitoring guidance, and the skippy-quantize README.

Direct-return TCP server removal across prompt, bench, and correctness clients

Layer / File(s) Summary
skippy-protocol sideband token and stats encoding
crates/skippy-protocol/src/binary/codec.rs, crates/skippy-protocol/src/binary/mod.rs
Serializes predicted-token sideband vectors, rewrites reply-stats encoding, and adds a sideband token round-trip test.
skippy-prompt direct-return removal
crates/skippy-prompt/src/prompt_cli/...
Removes direct-return server wiring and switches reply receipt to the existing TCP stream with reply-kind checks.
skippy-bench direct-return removal
crates/skippy-bench/src/distributed.rs, crates/skippy-bench/src/local_split.rs, crates/skippy-bench/src/main.rs
Removes direct-return server usage, replaces it with inline reply validation, and switches topology driver wiring to a static driver endpoint.
host runtime direct-return removal
crates/mesh-llm-host-runtime/src/inference/skippy/mod.rs, crates/mesh-llm-host-runtime/src/inference/skippy/resolver/translation.rs
Removes PredictionReturnListener and prediction_returns wiring from the embedded OpenAI host runtime.

Native MTP N1 correctness testing and verify-span-local benchmark

Layer / File(s) Summary
skippy-ffi ABI and MTP decode symbols
crates/skippy-ffi/src/lib.rs, crates/skippy-ffi/README.md
Bumps the ABI version, adds MTP-N1 support, expands sampled decode/verify symbols, and updates the documented execution surface.
model-artifact GGUF metadata for nextn_predict_layers
crates/model-artifact/src/gguf.rs
Adds nextn_predict_layers to compact metadata parsing and tests.
skippy-correctness report model extensions
crates/skippy-correctness/src/report.rs
Extends correctness reports with second-predicted-token and native MTP sideband fields, plus new OpenAI A/B report structs.
skippy-correctness native MTP verification flow
crates/skippy-correctness/src/runner.rs
Threads native MTP requirements through correctness runs, computes second predicted tokens and N1 verification outcomes, and removes direct-return wiring from split/chain paths.
skippy-correctness CLI, module wiring, and docs for native MTP jobs
crates/skippy-correctness/src/cli.rs, crates/skippy-correctness/src/main.rs, crates/skippy-correctness/Cargo.toml, crates/skippy-correctness/README.md
Adds the native MTP OpenAI A/B command and supporting args, wires the module into the binary, adds reqwest, and documents the command usage.
native_mtp_openai orchestration, remote stage1, metrics, and reporting
crates/skippy-correctness/src/native_mtp_openai/...
Implements the full three-case orchestration, stage lifecycle, remote SSH launch/guarding, metrics parsing, and report emission.
skippy-bench verify-span-local benchmark
crates/skippy-bench/src/cli.rs, crates/skippy-bench/src/verify_span_local.rs, crates/skippy-bench/src/main.rs
Adds the local verify-span benchmark command, the timing/reporting implementation, and the main dispatch wiring.

skippy-model-package artifact hook fix and SPD project handoff document

Layer / File(s) Summary
skippy-model-package artifact hook ordering
crates/skippy-model-package/src/main.rs
Runs the artifact hook before reopening the output file and re-reads tensor metadata from the actual written artifact.
SPD_SKIPPY_PROJECT handoff document
SPD_SKIPPY_PROJECT.md
Adds the SPD-on-MTP project handoff document with current status, layout, reproduction commands, milestones, and validation guidance.

Possibly related issues

Possibly related PRs

  • Mesh-LLM/mesh-llm#858: Shares the direct-return removal pattern and nextn_predict_layers GGUF metadata parsing changes.
  • Mesh-LLM/mesh-llm#898: Overlaps with the new skippy-quantize crate, native GGUF conversion/quantization pipeline, and CLI wiring.

Suggested reviewers

  • ndizazzo
  • michaelneale

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~180 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 29.56% 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 PR title 'Add resumable skippy quantize workflows' clearly and concisely describes the main change: adding resumable GGUF quantization workflows.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/jianyang-skippy-quantize
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch feat/jianyang-skippy-quantize

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: 1

Note

Due to the large number of review comments, Critical severity comments were prioritized as inline comments.

🟠 Major comments (23)
crates/skippy-correctness/src/native_mtp_openai/remote.rs-65-69 (1)

65-69: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Harden SSH/SCP invocations to avoid orchestration hangs.

Line 65 and the helpers at Line 208-242 run remote commands without non-interactive/timeout SSH options. In unattended runs this can block indefinitely on auth/host-key prompts or long network stalls.

Suggested fix
-    let output = Command::new("ssh")
-        .arg(host)
+    let output = Command::new("ssh")
+        .args(["-o", "BatchMode=yes", "-o", "ConnectTimeout=10"])
+        .arg(host)
         .arg(command)
         .output()
         .with_context(|| format!("start remote stage 1 on {host}"))?;

 fn ssh_success(host: &str, remote_command: &str) -> Result<()> {
     let status = Command::new("ssh")
+        .args(["-o", "BatchMode=yes", "-o", "ConnectTimeout=10"])
         .arg(host)
         .arg(remote_command)
         .status()
         .with_context(|| format!("run ssh command on {host}"))?;

 fn scp_to(host: &str, local_path: &Path, remote_path: &str) -> Result<()> {
     let status = Command::new("scp")
+        .args(["-o", "BatchMode=yes", "-o", "ConnectTimeout=10"])
         .arg(local_path)
         .arg(format!("{host}:{remote_path}"))
         .status()
         .with_context(|| format!("copy {} to {host}:{remote_path}", local_path.display()))?;

 fn scp_from(host: &str, remote_path: &str, local_path: &Path) -> Result<()> {
     let status = Command::new("scp")
+        .args(["-o", "BatchMode=yes", "-o", "ConnectTimeout=10"])
         .arg(format!("{host}:{remote_path}"))
         .arg(local_path)
         .status()
         .with_context(|| format!("copy {host}:{remote_path} to {}", local_path.display()))?;

Also applies to: 208-242

🤖 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 `@crates/skippy-correctness/src/native_mtp_openai/remote.rs` around lines 65 -
69, The SSH command invocation in the remote stage execution needs to be
hardened with non-interactive and timeout options to prevent indefinite blocking
on authentication prompts or network stalls. Add SSH arguments like -o
BatchMode=yes for non-interactive mode and -o ConnectTimeout with an appropriate
timeout value to the Command::new("ssh") invocation at line 65, and apply the
same hardening to all SSH/SCP command invocations in the helper functions
located at lines 208-242 to ensure consistent behavior across all remote command
execution paths.
crates/skippy-correctness/src/native_mtp_openai/mod.rs-65-70 (1)

65-70: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Require non-loopback stage1 networking when --stage1-ssh-host is used.

Line 65-70 validates remote mode flags, but it doesn’t validate addresses. With current defaults (stage1_bind_addr=127.0.0.1:19172, stage1_endpoint_addr unset), enabling --stage1-ssh-host makes stage0 target loopback and remote stage1 is unreachable by default.

Suggested fix
     if args.stage1_ssh_host.is_some() && args.stage1_remote_stage_server_bin.is_none() {
         bail!("--stage1-remote-stage-server-bin is required with --stage1-ssh-host");
     }
+    if args.stage1_ssh_host.is_some() {
+        let stage1_bind = args.stage1_bind_addr;
+        let stage1_endpoint = args.stage1_endpoint_addr.unwrap_or(stage1_bind);
+        if stage1_bind.ip().is_loopback() || stage1_endpoint.ip().is_loopback() {
+            bail!(
+                "--stage1-bind-addr and --stage1-endpoint-addr must be non-loopback when --stage1-ssh-host is set"
+            );
+        }
+    }
🤖 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 `@crates/skippy-correctness/src/native_mtp_openai/mod.rs` around lines 65 - 70,
When --stage1-ssh-host is specified, the validation logic needs to check that
the networking addresses are configured for remote access. Add a validation
check in the section after line 70 (following the existing args.stage1_ssh_host
validations) to ensure that when args.stage1_ssh_host is some value, the
stage1_bind_addr must not be a loopback address (127.0.0.1) and
stage1_endpoint_addr must be set to a non-loopback value. This prevents the
incompatible default configuration where stage0 would target loopback while
attempting to reach a remote stage1 server.
crates/skippy-correctness/src/runner.rs-2956-2962 (1)

2956-2962: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Preflight selects non-GGUF stage_model files and can fail valid runs.

Line 2956 currently picks runtime.stage_model whenever it is a file. In artifact-slice usage, --stage-model can be a manifest file, so Lines 2964-2969 then try GGUF metadata/tensor inspection on a non-GGUF path and fail before execution. Gate this path to GGUF files (or resolve to an actual GGUF) before calling native_mtp_artifact_summary.

💡 Suggested fix
 fn native_mtp_preflight_model_path(runtime: &RuntimeArgs) -> &Path {
     runtime
         .stage_model
         .as_deref()
-        .filter(|path| path.is_file())
+        .filter(|path| {
+            path.is_file()
+                && path
+                    .extension()
+                    .and_then(|ext| ext.to_str())
+                    .is_some_and(|ext| ext.eq_ignore_ascii_case("gguf"))
+        })
         .unwrap_or(runtime.model.as_path())
 }

Also applies to: 2964-2969

🤖 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 `@crates/skippy-correctness/src/runner.rs` around lines 2956 - 2962, The
`native_mtp_preflight_model_path` function currently accepts any file in
`runtime.stage_model` without validating it is actually a GGUF file. This causes
downstream GGUF metadata inspection to fail when a manifest file is passed as
`--stage-model`. Modify the filter condition in
`native_mtp_preflight_model_path` to check that the selected path is
specifically a GGUF file (not just any file) before returning it. If
`stage_model` is not a GGUF file, it should fall back to
`runtime.model.as_path()` instead. This ensures only valid GGUF paths are passed
to the code at lines 2964-2969 that performs tensor inspection.
crates/skippy-correctness/src/runner.rs-957-998 (1)

957-998: 📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

Extract the duplicated decode/verification flow into a shared helper.

The staged decode + optional second-step verification logic is duplicated across Line 957+ and Line 1276+. Consolidating this will reduce drift and keep these orchestration functions within maintainable complexity boundaries.

As per coding guidelines: "**/*.rs: Do not add Rust methods or functions over the configured Clippy line-count limit...andDo not add Rust code over the configured cognitive-complexity limit. Prefer small, named decision helpers...`"

Also applies to: 1276-1317

🤖 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 `@crates/skippy-correctness/src/runner.rs` around lines 957 - 998, Extract the
duplicated staged decode and verification flow into a shared helper function in
the runner.rs file. Identify the common pattern used in both the initial decode
block (around line 957) and the second verification block (around line 1276)
that involves calling binary_decode_message with specific
BinaryDecodeMessageArgs, write_stage_message, recv_reply, and ensure_reply_kind
functions. Create a helper function that accepts parameters like the stream,
session, wire_dtype, token_id, decode_step, activation_width, request_id, and
session_id, and returns the predicted token and optional timing information.
Replace both duplicated code sections with calls to this new helper function,
passing the appropriate parameters for each context (decode_step 0 for the first
call, decode_step 1 for the verification call).

Source: Coding guidelines

crates/skippy-quantize/scripts/compare-reference-quantization.py-65-97 (1)

65-97: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Fail fast when the invocation would run zero checks.

Right now the script can exit 0 after writing a report even when neither conversion nor quantization actually ran (for example, missing both --checkpoint and --quant-input, or --checkpoint without a converter and no quant input). Add argument validation so “no-op success” cannot happen.

Suggested fix
 def parse_args() -> argparse.Namespace:
     parser = argparse.ArgumentParser()
@@
-    return parser.parse_args()
+    args = parser.parse_args()
+    if args.checkpoint is None and args.quant_input is None:
+        parser.error("provide --checkpoint and/or --quant-input")
+    if (
+        args.checkpoint is not None
+        and args.python_converter is None
+        and args.quant_input is None
+    ):
+        parser.error(
+            "--checkpoint requires --python-converter when --quant-input is not provided"
+        )
+    return args
🤖 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 `@crates/skippy-quantize/scripts/compare-reference-quantization.py` around
lines 65 - 97, The main function can exit successfully without performing any
actual work when neither conversion nor quantization runs. Add argument
validation immediately after parse_args() in the main function to ensure at
least one meaningful check would execute. Validate that either args.checkpoint
is not None or args.quant_input is not None, and raise an error or print a usage
message if neither is provided. This validation should fail fast before any work
is attempted, preventing the script from writing an empty report and returning
success when no actual checks were performed.
.agents/skills/hf-quant-and-layer-package-jobs/SKILL.md-42-75 (1)

42-75: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

--max-memory in the llama-api examples will fail at runtime.

Line 45 and Line 73 pair --backend llama-api with --max-memory 32G, but the native llama quantize path rejects --max-memory (crates/skippy-quantize/src/native_quantize.rs, Line 159). These documented commands currently fail before execution.

Suggested doc correction
 target/release/skippy-quantize run-quant \
   --manifest /tmp/skippy-quantize.json \
   --backend llama-api \
-  --max-memory 32G \
   --work-dir /tmp/skippy-quantize-work \
   --spool-dir /tmp/skippy-quantize-output \
   --record-dir /tmp/skippy-quantize-records \
   --json-event-file /tmp/skippy-quantize-status.json \
   --json-event-interval-seconds 120 \
   --json-event-window 8
@@
 target/release/skippy-quantize quant-job \
   --source /mnt/bf16 \
   --source-prefix BF16 \
   --target /mnt/quant \
   --target-prefix <quant-selector> \
   --output-basename <model>-<quant-selector> \
   --quant <quant-selector> \
   --tensor-type-file /mnt/recipe/tensor-types.txt \
   --window-size 1 \
   --manifest /tmp/skippy-quantize.json \
   --backend llama-api \
-  --max-memory 32G \
   --dry-run
🤖 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 @.agents/skills/hf-quant-and-layer-package-jobs/SKILL.md around lines 42 -
75, The documentation contains two command examples (in the run-quant command
block and the quant-job command block) that pair `--backend llama-api` with
`--max-memory 32G`, but the llama quantize backend explicitly rejects the
`--max-memory` flag, causing these documented commands to fail at runtime.
Remove the `--max-memory 32G` parameter from both the skippy-quantize run-quant
command and the skippy-quantize quant-job command that use `--backend llama-api`
to make the documented examples executable.
crates/skippy-quantize/src/plan_convert.rs-43-50 (1)

43-50: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Validate --stream-buffer-bytes before entering streaming paths.

Line 47/58/78 forward stream_buffer_bytes directly, but this command never rejects 0. Add the same invariant used by other convert entrypoints to prevent invalid streaming behavior.

💡 Proposed fix
-use anyhow::Result;
+use anyhow::{Result, ensure};
...
 pub(crate) fn run_plan_convert(args: PlanConvertArgs) -> Result<()> {
+    ensure!(
+        args.stream_buffer_bytes > 0,
+        "--stream-buffer-bytes must be greater than zero"
+    );
     let mut plan = inspect_hf_checkpoint(&args.source, args.max_memory, args.staging_fraction)?;

Also applies to: 58-59, 78-79

🤖 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 `@crates/skippy-quantize/src/plan_convert.rs` around lines 43 - 50, The
run_plan_convert function forwards stream_buffer_bytes to
verify_hf_checkpoint_tensor_streams without validating that it is a valid
non-zero value, which can lead to invalid streaming behavior. Add validation
logic to check that args.stream_buffer_bytes is greater than zero before
entering the streaming path (before the condition checking
args.verify_streaming). This validation should reject zero or invalid values
similar to how other convert entrypoints handle this parameter to prevent
invalid streaming operations on lines 49-51 and any other locations where
stream_buffer_bytes is forwarded to streaming functions.
crates/skippy-quantize/src/main.rs-679-687 (1)

679-687: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

quantize-layer-package silently drops quantization options that change output artifacts.

This path accepts QuantRunnerArgs but the generated hook command ignores output-affecting flags (for example --imatrix, --include-weights/--exclude-weights, --tensor-type, --output-tensor-type, --token-embedding-type, --prune-layers, --override-kv). That can produce different quantized artifacts than requested with no error.

💡 Minimal safe fix (reject unsupported flags until fully forwarded)
 fn quantize_layer_package(args: QuantizeLayerPackageArgs) -> Result<()> {
@@
     ensure!(
         !args.runner.print_only,
         "quantize-layer-package does not support --print-only; use quant-job --preflight-only first"
     );
+    ensure!(
+        args.runner.imatrix.is_none()
+            && args.runner.include_weights.is_empty()
+            && args.runner.exclude_weights.is_empty()
+            && args.runner.output_tensor_type.is_none()
+            && args.runner.token_embedding_type.is_none()
+            && args.runner.tensor_type.is_empty()
+            && args.runner.prune_layers.is_none()
+            && args.runner.override_kv.is_empty(),
+        "quantize-layer-package currently does not forward all quantization override flags; remove overrides or add forwarding in hook generation"
+    );

Also applies to: 781-799

🤖 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 `@crates/skippy-quantize/src/main.rs` around lines 679 - 687, The
quantize_layer_package function accepts QuantRunnerArgs but silently ignores
output-affecting quantization flags like imatrix, include-weights,
exclude-weights, tensor-type, output-tensor-type, token-embedding-type,
prune-layers, and override-kv, which can produce different artifacts than
requested without error. Add ensure! checks (similar to the existing window_size
and dry_run validations) to validate that these unsupported flags are not being
passed, and reject them with descriptive error messages. Apply the same
validation logic to all locations where this issue occurs, including the other
instance referenced at lines 781-799.
crates/skippy-quantize/src/artifacts.rs-99-117 (1)

99-117: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Don’t treat same-size target shards as equivalent content.

The resume shortcut currently trusts size equality only. A stale or wrong shard with identical length is accepted and silently propagated.

Suggested fix
 fn publish_file(source: &Path, target: &Path) -> Result<()> {
@@
     if target.exists() {
         let target_len = target
@@
         ensure!(
             source_len == target_len,
             "target shard already exists with different size: {} source_bytes={} target_bytes={}",
             target.display(),
             source_len,
             target_len
         );
+        ensure!(
+            files_equal(source, target)?,
+            "target shard already exists with same size but different content: {}",
+            target.display()
+        );
         print_info(format!(
             "Publish target already exists: {} ({})",
             target.display(),
             format_bytes(source_len)
         ));
         return Ok(());
     }
@@
 }
+
+fn files_equal(a: &Path, b: &Path) -> Result<bool> {
+    const BUF: usize = 1024 * 1024;
+    let mut fa = fs::File::open(a).with_context(|| format!("open {}", a.display()))?;
+    let mut fb = fs::File::open(b).with_context(|| format!("open {}", b.display()))?;
+    let mut ba = vec![0_u8; BUF];
+    let mut bb = vec![0_u8; BUF];
+    loop {
+        let na = std::io::Read::read(&mut fa, &mut ba).with_context(|| format!("read {}", a.display()))?;
+        let nb = std::io::Read::read(&mut fb, &mut bb).with_context(|| format!("read {}", b.display()))?;
+        if na != nb {
+            return Ok(false);
+        }
+        if na == 0 {
+            return Ok(true);
+        }
+        if ba[..na] != bb[..nb] {
+            return Ok(false);
+        }
+    }
+}
🤖 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 `@crates/skippy-quantize/src/artifacts.rs` around lines 99 - 117, The resume
shortcut logic in the target shard validation currently only compares file sizes
between source and target, which is insufficient because two files with
identical length may have different content. Instead of relying solely on the
`source_len == target_len` equality check, implement content verification by
computing and comparing checksums or hashes of both the source and target files.
This ensures that stale or corrupted shards with the same byte length are not
silently accepted as equivalent content.
crates/skippy-quantize/src/output.rs-289-339 (1)

289-339: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Avoid temp-file collisions between periodic and manual snapshot writes.

write_now and the background writer can call write_snapshot concurrently, but both use the same temp filename. That introduces rename races and intermittent write errors.

Suggested fix
 fn write_snapshot(path: &Path, state: &Arc<Mutex<JsonEventState>>) -> Result<()> {
@@
-    let temp = path.with_extension(format!(
-        "{}.tmp",
-        path.extension()
-            .and_then(|extension| extension.to_str())
-            .unwrap_or("json")
-    ));
+    let temp = path.with_extension(format!(
+        "{}.{}.{}.tmp",
+        path.extension()
+            .and_then(|extension| extension.to_str())
+            .unwrap_or("json"),
+        std::process::id(),
+        unix_timestamp_ms()
+    ));
🤖 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 `@crates/skippy-quantize/src/output.rs` around lines 289 - 339, The
write_snapshot function creates a temp file with a fixed naming pattern that
doesn't account for concurrent calls. When both the periodic writer thread (from
spawn_periodic_writer) and the write_now function call write_snapshot
simultaneously using the same path, they both try to write to the same temp
filename, causing a rename race condition. Modify the temp filename generation
to include a unique identifier (such as a random component, UUID, or thread ID)
so that concurrent calls to write_snapshot generate different temp filenames
before atomically renaming them to the final path.
crates/skippy-quantize/src/backend.rs-208-216 (1)

208-216: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Gate quant backends on actual runtime capability, not just enum kind.

This check currently accepts llama-api/skippy-abi even when their runtime is unavailable, so invalid backend choices fail later instead of at preflight.

Suggested fix
 pub fn ensure_quant_backend(kind: BackendKind) -> Result<()> {
-    ensure!(
-        matches!(kind, BackendKind::LlamaApi | BackendKind::SkippyAbi),
-        "backend {} cannot quantize GGUFs yet: {}",
-        kind.as_str(),
-        capabilities(&[]).skippy_abi.reason
-    );
+    let caps = capabilities(&[]);
+    match kind {
+        BackendKind::LlamaApi => ensure!(
+            caps.llama_api.llama_quantize,
+            "backend {} is unavailable: {}",
+            kind.as_str(),
+            caps.llama_api.reason
+        ),
+        BackendKind::SkippyAbi => ensure!(
+            caps.skippy_abi.llama_quantize,
+            "backend {} is unavailable: {}",
+            kind.as_str(),
+            caps.skippy_abi.reason
+        ),
+        BackendKind::NativeRust => ensure!(
+            false,
+            "backend {} cannot quantize GGUFs yet",
+            kind.as_str()
+        ),
+    }
     Ok(())
 }
🤖 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 `@crates/skippy-quantize/src/backend.rs` around lines 208 - 216, The
ensure_quant_backend function currently only validates the enum kind against
allowed types (BackendKind::LlamaApi and BackendKind::SkippyAbi) but does not
check if these backends are actually available at runtime. Fix this by using the
capabilities function to get the runtime capabilities for the provided backend
kind and verify that the specific backend has the required capability enabled
(not just checking the enum type with matches!). This ensures that backends pass
validation only if they are both the correct enum type AND actually available at
runtime, catching invalid backend choices at preflight instead of later in
execution.
crates/skippy-quantize/src/hf_checkpoint.rs-359-367 (1)

359-367: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Constrain indexed shard paths to stay under the checkpoint root.

weight_map entries are joined directly into filesystem paths, so absolute paths or .. segments can escape the checkpoint directory and make this command read arbitrary local files.

Suggested fix
 fn discover_indexed_safetensors(source: &Path) -> Result<Vec<PathBuf>> {
@@
-    let mut files = index
+    let source_root = source
+        .canonicalize()
+        .with_context(|| format!("canonicalize {}", source.display()))?;
+    let mut files = index
         .weight_map
         .values()
-        .map(|name| source.join(name))
+        .map(|name| {
+            let candidate = source.join(name);
+            let canonical = candidate
+                .canonicalize()
+                .with_context(|| format!("canonicalize {}", candidate.display()))?;
+            ensure!(
+                canonical.starts_with(&source_root),
+                "indexed shard path escapes checkpoint root: {}",
+                candidate.display()
+            );
+            Ok(canonical)
+        })
+        .collect::<Result<BTreeSet<_>>>()?
-        .collect::<BTreeSet<_>>()
         .into_iter()
         .collect::<Vec<_>>();
🤖 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 `@crates/skippy-quantize/src/hf_checkpoint.rs` around lines 359 - 367, The code
in this block joins weight_map entries directly into filesystem paths without
validating that they remain within the checkpoint directory boundaries. This
creates a security vulnerability where entries containing absolute paths or `..`
segments can escape the checkpoint root directory. To fix this, validate each
file name from weight_map.values() before joining it with source: check that
each name is relative (not absolute) and does not contain path components like
`..` that would escape the directory, then normalize the resulting joined path
and verify it stays within the source directory using canonicalization or path
comparison techniques before including it in the files collection.
crates/skippy-quantize/src/imatrix.rs-142-166 (1)

142-166: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Validate length-prefixed fields before allocating.

The parser trusts file-provided entry_count, name_len, value_count, dataset length, and GGUF string length before allocation. A corrupt imatrix can request huge Vec allocations before read_exact fails.

Proposed direction
+fn checked_remaining_len(reader: &Cursor<Vec<u8>>, len: u64, what: &str) -> Result<usize> {
+    let len = usize::try_from(len).with_context(|| format!("{what} length does not fit usize"))?;
+    let position = usize::try_from(reader.position()).context("reader position does not fit usize")?;
+    let remaining = reader.get_ref().len().saturating_sub(position);
+    ensure!(len <= remaining, "{what} extends past end of imatrix file");
+    Ok(len)
+}
+
 fn read_gguf_string(reader: &mut Cursor<Vec<u8>>) -> Result<String> {
     let len = read_u64(reader)?;
-    let mut bytes = vec![0_u8; len as usize];
+    let len = checked_remaining_len(reader, len, "GGUF string")?;
+    let mut bytes = vec![0_u8; len];
     reader.read_exact(&mut bytes)?;
     String::from_utf8(bytes).context("GGUF string is not UTF-8")
 }

Apply the same check before allocating legacy names, values, and dataset bytes.

Also applies to: 186-192, 476-480

🤖 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 `@crates/skippy-quantize/src/imatrix.rs` around lines 142 - 166, Add validation
checks for length-prefixed fields before allocating memory to prevent potential
out-of-memory attacks from corrupted files. In the read_legacy_imatrix_entry
function, add an ensure! check for name_len before allocating the name_bytes
vector to verify it is within a reasonable maximum size. Similarly, validate
value_count before allocating the values vector. Apply the same validation
pattern in read_legacy_imatrix_trailer for the dataset length and any other
length-prefixed allocations to ensure all file-provided lengths are checked for
reasonableness before Vec allocation occurs.
crates/skippy-quantize/src/gguf_template.rs-27-35 (1)

27-35: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Mirror mtp_num_hidden_layers in emitted MTP metadata.

Line 28 treats mtp_num_hidden_layers as a valid MTP declaration, but Lines 141-143 and 56-62 only count/emit num_nextn_predict_layers. A config that only uses mtp_num_hidden_layers can therefore select MTP tensors while writing a base-only {arch}.block_count, leaving blk.* tensor indices beyond the advertised metadata.

Proposed fix
 pub(crate) fn mtp_layer_start_from_hf_config(source: &Path) -> Result<Option<u32>> {
     let config = read_hf_config(source)?;
-    let Some(nextn_layers) = optional_u32(&config, "num_nextn_predict_layers")
-        .or_else(|| optional_u32(&config, "mtp_num_hidden_layers"))
-    else {
+    let Some(_nextn_layers) = optional_mtp_layer_count(&config) else {
         return Ok(None);
     };
-    if nextn_layers == 0 {
-        return Ok(None);
-    }
     required_u32(&config, "num_hidden_layers").map(Some)
 }
@@
     if options.include_mtp {
-        push_if_u32(
-            &mut metadata,
-            arch,
-            "nextn_predict_layers",
-            &config,
-            "num_nextn_predict_layers",
-        );
+        if let Some(nextn_layers) = optional_mtp_layer_count(&config) {
+            metadata.push(GgufKv::u32(
+                &format!("{arch}.nextn_predict_layers"),
+                nextn_layers,
+            ));
+        }
     }
@@
     let block_count = required_u32(config, "num_hidden_layers")?
         + if options.include_mtp {
-            optional_u32(config, "num_nextn_predict_layers").unwrap_or(0)
+            optional_mtp_layer_count(config).unwrap_or(0)
         } else {
             0
         };
@@
 fn optional_u32(config: &Value, key: &str) -> Option<u32> {
@@
 }
+
+fn optional_mtp_layer_count(config: &Value) -> Option<u32> {
+    optional_u32(config, "num_nextn_predict_layers")
+        .or_else(|| optional_u32(config, "mtp_num_hidden_layers"))
+        .filter(|count| *count > 0)
+}

Also applies to: 55-63, 140-146

🤖 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 `@crates/skippy-quantize/src/gguf_template.rs` around lines 27 - 35, The
metadata emission code in lines 56-62 and 140-146 only checks for
`num_nextn_predict_layers` when writing the MTP block count, but the input
parsing in the nextn_layers block accepts either `num_nextn_predict_layers` or
`mtp_num_hidden_layers`. Mirror the same fallback logic from the nextn_layers
variable assignment (checking `num_nextn_predict_layers` first, then falling
back to `mtp_num_hidden_layers`) in both metadata emission sections so that
configs using only `mtp_num_hidden_layers` will correctly emit the appropriate
block count metadata.
crates/skippy-quantize/src/gguf_writer.rs-317-329 (1)

317-329: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Reject duplicate GGUF tensor names after mapping.

Different HF names can normalize to the same GGUF name, such as prefixed/unprefixed shared tensors or duplicated shard entries. The writer currently emits both entries, producing an ambiguous GGUF tensor table.

Proposed fix
     for group in expert_groups.into_values() {
         tensors.push(group.into_tensor_source()?);
     }
     tensors.sort_by(|a, b| a.name.cmp(&b.name));
+    for pair in tensors.windows(2) {
+        ensure!(
+            pair[0].name != pair[1].name,
+            "duplicate GGUF tensor name {} after tensor name mapping",
+            pair[0].name
+        );
+    }
     Ok(tensors)
 }
🤖 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 `@crates/skippy-quantize/src/gguf_writer.rs` around lines 317 - 329, The
current implementation collects tensors from safetensor files and expert groups
into the tensors vector, then sorts by name, but does not check for duplicate
GGUF tensor names that may result from different HF names mapping to the same
GGUF name. After the tensors.sort_by call, add validation logic to detect and
reject any duplicate tensor names by iterating through the sorted tensors vector
and ensuring each entry has a unique name. If duplicates are found, return an
error that identifies the conflicting names to aid debugging.
crates/skippy-quantize/src/imatrix.rs-222-226 (1)

222-226: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Reject GGUF chunk_count values that do not fit the FFI field.

Line 225 casts u32 to i32, so values above i32::MAX silently become negative before being exposed through NativeImatrix::chunk_count().

Proposed fix
     Ok(LoadedImatrix {
         entries,
         dataset: metadata.datasets.into_iter().next(),
-        chunk_count: metadata.chunk_count.unwrap_or(0) as i32,
+        chunk_count: metadata
+            .chunk_count
+            .map(i32::try_from)
+            .transpose()
+            .context("imatrix.chunk_count does not fit int32")?
+            .unwrap_or(0),
     })
🤖 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 `@crates/skippy-quantize/src/imatrix.rs` around lines 222 - 226, The
chunk_count field is casting a u32 value directly to i32 without validation,
which causes silent overflow when the value exceeds i32::MAX. Before the cast in
the LoadedImatrix construction, validate that metadata.chunk_count.unwrap_or(0)
does not exceed i32::MAX, and if it does, return an appropriate error. Only
proceed with the i32 cast if the validation passes.
crates/skippy-quantize/src/imatrix.rs-419-424 (1)

419-424: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Apply include and exclude filters together.

When both filters are provided, Line 420 ignores include_weights and keeps every non-excluded tensor. That can feed unrelated imatrix entries into quantization.

Proposed fix
 fn include_exclude_match(
     name: &str,
     include_weights: &[String],
     exclude_weights: &[String],
 ) -> bool {
-    if !exclude_weights.is_empty() {
-        return !exclude_weights.iter().any(|filter| name.contains(filter));
-    }
-    if !include_weights.is_empty() {
-        return include_weights.iter().any(|filter| name.contains(filter));
-    }
-    true
+    let included =
+        include_weights.is_empty() || include_weights.iter().any(|filter| name.contains(filter));
+    let excluded = exclude_weights.iter().any(|filter| name.contains(filter));
+    included && !excluded
 }
🤖 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 `@crates/skippy-quantize/src/imatrix.rs` around lines 419 - 424, The filter
logic currently returns early when exclude_weights is not empty, preventing
include_weights from being evaluated when both filters are provided. Fix this by
restructuring the logic to first check if the name should be excluded (return
false if it matches any exclude filter), then check if an include filter exists
and return false if the name does not match any include filter, and finally
return true if neither exclusion condition is met. This ensures both filters are
applied together: a tensor is only included if it passes both the exclude check
(not in exclude list) and the include check (in include list if one is
provided).
crates/llama-quant-ffi/build.rs-25-44 (1)

25-44: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Do not reuse native archives without validating the build inputs.

Line 94 returns as soon as archive files exist, so changes to native build inputs can keep linking stale artifacts, especially with a reused LLAMA_STAGE_BUILD_DIR. The script context shows additional inputs such as native flags, CUDA architectures, ROCm targets, and VMM settings that are not all tracked here. Let scripts/build-llama.sh’s stamp decide freshness, or compare the same stamp inputs before returning.

Proposed direction
 fn print_rerun_envs() {
     for key in [
         "LLAMA_STAGE_BUILD_DIR",
         "LLAMA_STAGE_LIB_DIR",
         "LLAMA_STAGE_LINK_MODE",
@@
         "LLVMInstallDir",
         "VULKAN_SDK",
+        "LLAMA_STAGE_GGML_NATIVE",
+        "SKIPPY_GGML_NATIVE",
+        "LLAMA_STAGE_CUDA_ARCHITECTURES",
+        "SKIPPY_CUDA_ARCHITECTURES",
+        "LLAMA_STAGE_AMDGPU_TARGETS",
+        "SKIPPY_AMDGPU_TARGETS",
+        "GGML_CUDA_NO_VMM",
+        "LLAMA_STAGE_USE_SCCACHE",
+        "SKIPPY_USE_SCCACHE",
+        "CMAKE_BUILD_TYPE",
     ] {
         println!("cargo:rerun-if-env-changed={key}");
     }
 }
@@
-    if required_static_archives_exist(build_dir) {
+    if required_static_archives_exist(build_dir) && !native_auto_build_enabled() {
         return;
     }

If always invoking the scripts when auto-build is enabled is too eager, replace the second hunk with an explicit build-stamp comparison using the same inputs as scripts/build-llama.sh.

Also applies to: 94-96

🤖 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 `@crates/llama-quant-ffi/build.rs` around lines 25 - 44, The print_rerun_envs
function in build.rs is incomplete and does not track all necessary build inputs
that affect native compilation. The build script at line 94 returns early when
archive files exist without validating that all build inputs remain unchanged,
allowing stale artifacts to be reused. Expand the print_rerun_envs function to
include tracking for all build inputs mentioned in the build process such as
native flags, CUDA architectures, ROCm targets, and VMM settings that
scripts/build-llama.sh uses. Ensure these environment variables and
configuration inputs are added to the array of keys being monitored with
cargo:rerun-if-env-changed directives so that the build system will properly
detect when native build inputs change and invalidate cached archives.
crates/llama-quant-ffi/build.rs-139-149 (1)

139-149: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Make native auto-build opt-in.

Line 148 makes plain Cargo builds clone/build patched llama.cpp when archives are missing. That can break offline builds and unexpectedly turn cargo check/CI into a long native build; the existing panic text already describes enabling auto-build explicitly.

Proposed fix
 fn native_auto_build_enabled() -> bool {
     for key in ["SKIPPY_LLAMA_AUTO_BUILD", "MESH_LLM_AUTO_BUILD_LLAMA"] {
         if let Ok(value) = std::env::var(key) {
             return !matches!(
                 value.to_ascii_lowercase().as_str(),
                 "0" | "false" | "no" | "off"
             );
         }
     }
-    true
+    false
 }
🤖 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 `@crates/llama-quant-ffi/build.rs` around lines 139 - 149, The
native_auto_build_enabled() function currently defaults to returning true when
neither SKIPPY_LLAMA_AUTO_BUILD nor MESH_LLM_AUTO_BUILD_LLAMA environment
variables are set, making auto-build opt-out by default. This causes unexpected
long native builds during plain Cargo builds. Change the final return statement
from true to false so that auto-build is opt-in instead of opt-out, requiring
users to explicitly enable it via environment variables rather than having it
enabled by default.
Cargo.toml-50-57 (1)

50-57: 📐 Maintainability & Code Quality | 🟠 Major

Add the new workspace crates to workflow filters, publish scripts, and xtask consistency checks.

The root workspace now includes crates/skippy-quantize and crates/llama-quant-ffi in Cargo.toml, and they are correctly listed in scripts/affected-crates.sh and scripts/plan-clippy-batches.sh. However, per the coding guidelines for workspace crate additions, the following files still need to be updated in the same commit:

  • Workflow filters in .github/workflows/ files
  • scripts/publish-crates.sh
  • tools/xtask/src/main.rs for repo-consistency expectations
🤖 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 `@Cargo.toml` around lines 50 - 57, The new workspace crates skippy-quantize
and llama-quant-ffi have been added to Cargo.toml but are missing from other
configuration files required for proper workspace management. Add these two new
crate names to the workflow filters in all relevant .github/workflows files,
include them in the scripts/publish-crates.sh script, and update
tools/xtask/src/main.rs to include them in the repo-consistency expectations to
ensure they are properly tracked across all build, test, and release processes.

Source: Coding guidelines

crates/skippy-quantize/src/manifest.rs-59-65 (1)

59-65: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Write manifests atomically to protect resumability.

Directly writing the target path can leave a truncated/corrupt manifest if interrupted mid-write. For resumable jobs, this should use temp-file + atomic rename semantics.

🤖 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 `@crates/skippy-quantize/src/manifest.rs` around lines 59 - 65, The
write_manifest function directly writes to the target path, which can result in
a truncated or corrupted manifest if the write operation is interrupted.
Instead, implement atomic writes by writing to a temporary file first and then
atomically renaming it to the target path. Create a temporary file in the same
directory as the target path (to ensure they are on the same filesystem for
atomic rename operations), serialize and write the manifest to this temp file,
then use atomic rename to move the temp file to the final path specified in the
path parameter. This ensures that if an interruption occurs during write, the
original manifest file remains intact and jobs can resume cleanly.
crates/skippy-quantize/src/splits.rs-211-218 (1)

211-218: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Enforce split-window bounds inside stage_source_window.

This path currently accepts invalid windows and proceeds (e.g., first_split = 0 or last_split > total), which can stage an unintended shard set instead of hard-failing.

Proposed fix
 pub fn stage_source_window(
     source: &Path,
     source_prefix: &str,
     first_source_shard: &Path,
     stage_path: &Path,
     window: SplitWindow,
     total: u32,
 ) -> Result<PathBuf> {
+    validate_split_window(window, total)?;
     remove_dir_if_exists(stage_path)?;
     let stage_root = prefixed_path(stage_path, source_prefix);

Also applies to: 224-243

🤖 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 `@crates/skippy-quantize/src/splits.rs` around lines 211 - 218, Add validation
for the split-window bounds at the beginning of the stage_source_window function
to enforce that the window parameter contains valid bounds before processing.
The validation should check that first_split is at least 1, last_split does not
exceed the total parameter, and first_split is less than or equal to last_split.
If any of these conditions are violated, the function should return an
appropriate error instead of proceeding with staging the shard set.
crates/skippy-quantize/src/locking.rs-70-73 (1)

70-73: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Do not silently disable manifest locking on non-Unix platforms.

Returning Ok(()) here permits concurrent writers with no exclusion, which can race manifest reads/writes. If cross-platform locking is not implemented yet, fail fast instead of giving a false safety signal.

Safer interim behavior
 #[cfg(not(unix))]
 fn lock_file(_file: &File) -> Result<()> {
-    Ok(())
+    anyhow::bail!("manifest locking is not implemented on this platform")
 }
🤖 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 `@crates/skippy-quantize/src/locking.rs` around lines 70 - 73, The lock_file
function guarded by #[cfg(not(unix))] silently returns Ok(()) without actually
implementing any file locking mechanism, which masks the lack of proper
exclusion and allows concurrent writes to potentially race. Instead of returning
Ok(()), make this function return an error to fail fast and prevent false safety
assumptions until proper cross-platform locking is implemented. Replace the
Ok(()) with an appropriate error result that clearly indicates locking is not
available on this platform.
🟡 Minor comments (3)
crates/skippy-protocol/src/binary/codec.rs-50-68 (1)

50-68: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Keep scalar and sideband predictions consistent.

This exported helper can encode predicted = 42 with predicted_tokens = [] or [7, ...], producing a split-brain StageReply after recv_reply. Preserve the old invariant by requiring the sideband to be non-empty and start with predicted.

Proposed fix
 pub fn send_reply_predicted_with_tokens_and_stats(
     mut writer: impl Write,
     predicted: i32,
     predicted_tokens: &[i32],
     stats: StageReplyStats,
 ) -> io::Result<()> {
+    if predicted_tokens.is_empty() {
+        return Err(invalid_input("predicted token reply requires at least one token"));
+    }
+    if predicted_tokens[0] != predicted {
+        return Err(invalid_input(
+            "predicted token sideband must start with predicted token",
+        ));
+    }
     if predicted_tokens.len() > MAX_STAGE_PREDICTED_TOKENS {
         return Err(invalid_input("too many predicted tokens"));
     }
🤖 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 `@crates/skippy-protocol/src/binary/codec.rs` around lines 50 - 68, The
send_reply_predicted_with_tokens_and_stats function currently allows
predicted_tokens to be empty or contain values inconsistent with the predicted
parameter, which creates a split-brain state. Add validation after the
MAX_STAGE_PREDICTED_TOKENS length check to ensure that predicted_tokens is
non-empty and that its first element equals the predicted value, returning an
invalid_input error with an appropriate message if either validation fails.
crates/skippy-quantize/scripts/compare-reference-quantization.py-109-110 (1)

109-110: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Validate --nthreads as a positive integer at parse time.

Accepting arbitrary strings here turns input mistakes into downstream command failures and can be misclassified as tolerable matching failures.

Suggested fix
-    parser.add_argument("--nthreads", default="8")
+    parser.add_argument("--nthreads", type=int, default=8)
@@
-    return parser.parse_args()
+    args = parser.parse_args()
+    if args.nthreads <= 0:
+        parser.error("--nthreads must be greater than zero")
+    return args
🤖 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 `@crates/skippy-quantize/scripts/compare-reference-quantization.py` around
lines 109 - 110, The --nthreads argument currently accepts any string value with
a default of "8" without type validation, causing input errors to manifest as
downstream failures instead of immediate validation errors. Modify the
parser.add_argument call for --nthreads to include a type parameter that
converts the input to an integer and a validation check (via choices or a custom
type function) to ensure only positive integers are accepted, so invalid inputs
are rejected at parse time rather than during execution.
crates/skippy-quantize/src/native_quantize.rs-259-277 (1)

259-277: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Preserve user tensor-pattern casing when building overrides.

Line 273 lowercases the pattern before FFI handoff. That can change regex meaning (\S\s, \W\w) and silently apply overrides to the wrong tensors.

Suggested fix
-        patterns.push(CString::new(raw_pattern.to_ascii_lowercase())?);
+        patterns.push(CString::new(raw_pattern)?);
🤖 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 `@crates/skippy-quantize/src/native_quantize.rs` around lines 259 - 277, The
pattern is being converted to lowercase with to_ascii_lowercase() before being
added to the patterns vector, which can change regex meaning and cause overrides
to be silently applied to the wrong tensors. Remove the to_ascii_lowercase()
call from the line where patterns.push(CString::new(...)) is invoked, and
instead pass raw_pattern directly to preserve the user's original tensor-pattern
casing.
🧹 Nitpick comments (3)
SPD_SKIPPY_PROJECT.md (2)

140-142: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Reduce repetitive sentence openers in the limitations list.

Lines 140–142 open three consecutive bullets with "Skippy/Skippy" subject positions, which reduces readability. Consider restructuring to vary the subject or tense.

- Skippy/Rust does not execute the SPD head.
- Skippy/Rust does not load tensor values into an executable SPD head yet.
- Skippy does not yet expose live hidden-state taps for SPD.
+ The SPD head has not yet been executed by Skippy/Rust.
+ Tensor loading for executable SPD head support is not yet implemented.
+ Skippy does not yet expose live hidden-state taps for SPD.
🤖 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 `@SPD_SKIPPY_PROJECT.md` around lines 140 - 142, The three consecutive
limitation bullets in the SPD_SKIPPY_PROJECT.md file all begin with
"Skippy/Rust" or "Skippy does", creating repetitive subject positions that hurt
readability. Restructure these three bullets (the ones describing SPD head
execution, tensor value loading, and hidden-state taps) by varying their
sentence structure and subject positions. Consider combining related points,
using passive voice alternatives, or restructuring the phrasing to eliminate the
repetitive opener pattern while maintaining clarity about what Skippy currently
cannot do.

217-241: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Clarify artifact licensing and handoff status in the layout section.

The "Artifact Layout Target" section proposes an eventual package structure but doesn't explicitly state whether the trained SPD head .safetensors file can be published or should remain internal. Milestone 5 (lines 395–401) mentions licensing constraints, but this guidance should be surfaced earlier here to prevent misunderstandings during reproduction.

Consider adding a note like:

+ **Publishing guidance:** Trained SPD head `.safetensors` files may be subject to base model licensing (e.g., Qwen). Before publishing a trained head, confirm compatibility with the base model's license. Serving manifests and metrics may be published even if the trained weights cannot.
🤖 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 `@SPD_SKIPPY_PROJECT.md` around lines 217 - 241, Add clarification to the
"Artifact Layout Target" section about the licensing status and handoff
constraints for the trained SPD head `.safetensors` file. Specifically, insert a
note that explicitly states whether the `.safetensors` artifact can be published
or should remain internal, and reference the licensing constraints mentioned in
Milestone 5 to make this guidance visible earlier in the document and prevent
misunderstandings during reproduction efforts.
crates/skippy-quantize/src/main.rs (1)

1049-1201: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Split the window-runner orchestration into phase helpers.

run_convert_window_once_with_manifest and run_quant_window_once_with_manifest each combine selection, planning, staging, execution, publishing, and cleanup in one block. Breaking these into named phase helpers will reduce cognitive load and make failure-path tests much easier to maintain.

As per coding guidelines: "Do not add Rust methods or functions over the configured Clippy line-count limit. Split long logic into semantically named helpers..." and "Do not add Rust code over the configured cognitive-complexity limit."

Also applies to: 1235-1443

🤖 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 `@crates/skippy-quantize/src/main.rs` around lines 1049 - 1201, The function
`run_convert_window_once_with_manifest` (and similarly
`run_quant_window_once_with_manifest`) is too long and has high cognitive
complexity, combining selection, planning, staging, execution, publishing, and
cleanup in a single block. Extract each distinct phase into semantically named
helper functions such as selecting the window, building the conversion plan,
preparing the output directory, executing the conversion command, and publishing
the results. This will reduce cognitive load, improve readability, and make
testing failure paths much easier to maintain.

Source: Coding guidelines

Comment on lines +72 to +80
let element_count = input.len() / source_dtype.byte_size() as usize;
let output_len = element_count
.checked_mul(target_dtype.byte_size() as usize)
.context("converted chunk byte length overflow")?;
let mut output = Vec::with_capacity(output_len);
for index in 0..element_count {
let value = read_float_element(input, source_dtype, index);
write_float_element(&mut output, target_dtype, value);
}

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.

🗄️ Data Integrity & Integration | 🔴 Critical | ⚡ Quick win

Reject misaligned chunks instead of silently dropping bytes.

element_count uses floor division, so non-aligned chunk sizes truncate trailing bytes and corrupt tensor conversion output.

Suggested fix
 pub(crate) fn convert_float_chunk<W: Write>(
@@
 ) -> Result<u64> {
-    let element_count = input.len() / source_dtype.byte_size() as usize;
+    let source_bytes = source_dtype.byte_size() as usize;
+    anyhow::ensure!(
+        input.len() % source_bytes == 0,
+        "chunk size {} is not aligned to source dtype width {}",
+        input.len(),
+        source_bytes
+    );
+    let element_count = input.len() / source_bytes;
🤖 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 `@crates/skippy-quantize/src/float_convert.rs` around lines 72 - 80, The
calculation of element_count using integer division silently drops trailing
bytes that don't form a complete element when the input length is not evenly
divisible by the source data type size. Add a validation check after calculating
element_count to ensure input.len() is evenly divisible by
source_dtype.byte_size() as usize, and use .context() to return an error if
there are misaligned bytes instead of allowing them to be truncated.

@i386

i386 commented Jun 23, 2026

Copy link
Copy Markdown
Collaborator Author

Closing in favor of the clean quant-only PR: #901

@i386 i386 closed this Jun 23, 2026
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