Skip to content

Add package writer progress output - #863

Merged
i386 merged 1 commit into
mainfrom
feat/jianyang
Jun 17, 2026
Merged

Add package writer progress output#863
i386 merged 1 commit into
mainfrom
feat/jianyang

Conversation

@i386

@i386 i386 commented Jun 17, 2026

Copy link
Copy Markdown
Collaborator

Summary

skippy-model-package write-package now reports deterministic package-writing progress while it builds layer package repos. The progress advances at artifact boundaries: shared metadata, embeddings, output, each layer GGUF, projector artifacts, and the final model-package.json.

The interactive renderer uses Ratatui's inline LineGauge instead of a bespoke progress bar, so this stays aligned with the terminal UI dependency already used by the project.

Output Preview

Interactive terminals redraw one stderr line while work is active. These examples are simulated from the actual Ratatui LineGauge renderer configured by this PR:

package  30.1% [25/83] writing ============----------------------------- layers/layer-022.gguf
package  31.3% [26/83] wrote =============------------------------------ layers/layer-022.gguf 4.4 GiB

For the GLM-5.1 Q3_K_M-plus layer package, the expected step count is:

3 shared artifacts + 79 layers + 1 manifest = 83 steps

A representative full progression looks like:

package   0.0% [0/83] writing ------------------------------------------ shared/metadata.gguf
package   1.2% [1/83] wrote -------------------------------------------- shared/metadata.gguf 9.0 MiB
package   2.4% [2/83] wrote =------------------------------------------- shared/embeddings.gguf 973.2 MiB
package   3.6% [3/83] wrote =------------------------------------------- shared/output.gguf 1.8 GiB
package   3.6% [3/83] writing =----------------------------------------- layers/layer-000.gguf
package   4.8% [4/83] wrote ==------------------------------------------ layers/layer-000.gguf 208.0 MiB
package  50.6% [42/83] wrote =====================---------------------- layers/layer-038.gguf 4.4 GiB
package  98.8% [82/83] wrote ==========================================- layers/layer-078.gguf 4.5 GiB
package  98.8% [82/83] writing ========================================- model-package.json
package 100.0% [83/83] wrote =========================================== model-package.json 62.7 KiB

Non-interactive logs emit one line per completed step instead of terminal redraws:

package progress: 42/83 wrote layers/layer-038.gguf 4.4 GiB

Progress output can be disabled with:

SKIPPY_MODEL_PACKAGE_PROGRESS=0 skippy-model-package write-package ...

Validation

cargo check -p skippy-model-package
cargo test -p skippy-model-package --bin skippy-model-package
cargo clippy -p skippy-model-package --bin skippy-model-package -- -D warnings

@coderabbitai

coderabbitai Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a new progress.rs module to skippy-model-package implementing PackageProgress, a step-based terminal progress reporter with TTY detection and env-var gating. Integrates it into write_package by wrapping all artifact writes (shared GGUFs, per-layer GGUFs, projectors, manifest) with start_step/finish_step calls and refactoring the projector iterator into an explicit loop.

Changes

skippy-model-package Progress Reporting

Layer / File(s) Summary
PackageProgress module: step lifecycle, bar rendering, byte formatting, and tests
crates/skippy-model-package/Cargo.toml, crates/skippy-model-package/src/progress.rs
Adds ratatui 0.30 dependency. New module introduces PackageProgress struct with new, start_step, finish_step, and finish methods. Conditionally emits an updating progress bar to stderr for interactive TTYs or plain log lines for non-interactive output, gated by SKIPPY_MODEL_PACKAGE_PROGRESS env var. Includes format_bytes binary unit formatter, safe percentage calculation, clamped gauge renderer using ratatui, and unit tests.
write_package instrumented with PackageProgress
crates/skippy-model-package/src/main.rs
Imports PackageProgress and format_bytes; initializes a progress instance sized to total artifact steps; wraps each shared GGUF, per-layer GGUF, projector copy (refactored from .map().collect() to an explicit loop), and manifest write with start_step/finish_step; calls progress.finish() at completion; adds artifact_progress_detail and projector_progress_detail helper functions.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.47% 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
Title check ✅ Passed The title 'Add package writer progress output' directly and accurately describes the main change: adding progress reporting functionality to the package writer command.
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ 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

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

@i386
i386 marked this pull request as ready for review June 17, 2026 01:10
@github-actions
github-actions Bot requested a review from ndizazzo June 17, 2026 01:10

@ndizazzo ndizazzo left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Seems fine, but you might consider inline progress bars from Ratatui, since they're already in the project instead of rolling a bespoke one

@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: 9

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
crates/skippy-model-package/src/main.rs (1)

481-659: 🛠️ Refactor suggestion | 🟠 Major | 🏗️ Heavy lift

Split write_package progress wiring out of this oversized module.

This file now extends past 2,000 lines (Line 2130), and the new progress-specific logic/helper additions further increase it. Please move the write_package progress orchestration (or at least the progress detail helpers and step-driving code) into a dedicated module to stay within the repository’s Rust file-size constraint.

As per coding guidelines, "Do not add Rust source files over 2,000 lines. If a file is approaching that size, split it by responsibility into an owning module instead of adding more code to the oversized 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 `@crates/skippy-model-package/src/main.rs` around lines 481 - 659, The main.rs
file exceeds the 2,000 line limit and needs to be refactored by extracting
progress-related code into a dedicated module. Move the write_package function
along with its helper functions artifact_progress_detail and
projector_progress_detail into a new module file (e.g., package_progress.rs or
package_writer.rs). Update the main.rs file to import and re-export these
functions from the new module, ensuring all necessary dependencies and type
imports are included in the new module. This will reduce the main.rs file size
and separate concerns by consolidating progress orchestration logic into its own
responsibility-focused module.

Source: Coding guidelines

crates/skippy-server/src/frontend.rs (1)

172-195: 🛠️ Refactor suggestion | 🟠 Major | 🏗️ Heavy lift

Keep new batcher wiring out of the over-limit frontend module.

frontend.rs is already over the 2,000-line limit. Please move the batcher construction/field assembly behind a focused backend-construction helper or module, leaving this file as routing/orchestration only. As per coding guidelines, “Do not add Rust source files over 2,000 lines.”

Also applies to: 522-544, 582-583

🤖 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-server/src/frontend.rs` around lines 172 - 195, The frontend.rs
file exceeds the 2,000-line limit per coding guidelines, and the batcher
construction and backend initialization logic (DecodeBatcher, DecodeFrameBatcher
instantiation, and StageOpenAiBackend struct initialization) is adding to this
violation. Extract the backend construction logic including the creation of
decode_batcher, decode_frame_batcher, and the entire StageOpenAiBackend struct
assembly into a separate focused helper function or dedicated module. This will
keep frontend.rs as a routing/orchestration-only file and reduce its overall
line count to comply with the guidelines. Apply the same refactoring principle
to any other similar constructions mentioned at lines 522-544 and 582-583.

Source: Coding guidelines

🧹 Nitpick comments (3)
crates/skippy-server/src/runtime_state.rs (1)

94-105: 💤 Low value

Consider adding #[derive(Debug)] for diagnostic purposes.

Other public structs in this file (e.g., RuntimeSessionLaneStats, RuntimeSessionDropStats) derive Debug. Adding it here would aid troubleshooting batch decode issues without any runtime cost.

♻️ Suggested change
+#[derive(Debug)]
 pub struct RuntimeDecodeBatchRequest<'a> {
     pub session_id: &'a str,
     pub token_id: i32,
     pub sampling: Option<&'a SamplingConfig>,
 }

+#[derive(Debug)]
 pub struct RuntimeDecodeFrameBatchRequest<'a> {
🤖 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-server/src/runtime_state.rs` around lines 94 - 105, The structs
RuntimeDecodeBatchRequest and RuntimeDecodeFrameBatchRequest do not derive
Debug, which is inconsistent with other public structs in the file like
RuntimeSessionLaneStats and RuntimeSessionDropStats. Add the #[derive(Debug)]
attribute to both struct definitions to enable diagnostic logging and debugging
without any performance cost.
crates/model-package/src/bin/queue-unsloth-layer-packages.rs (1)

444-446: 💤 Low value

Consider documenting --force behavior more explicitly in help text.

The --force flag bypasses all queue status checks (published, cataloged, queued), allowing re-queue of models regardless of their current state. A brief description would help users understand the implications.

📝 Suggested help text expansion
            --job-poll-seconds N\n\
            --split-candidate-vram-gib GiB\n\
-           --force\n\
+           --force (skip queue status checks; re-queue regardless of existing state)\n\
            --no-catalog-direct"

Also applies to: 456-456

🤖 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/model-package/src/bin/queue-unsloth-layer-packages.rs` around lines
444 - 446, The help text for the `--force` flag does not adequately explain its
behavior to users. Locate the help text definition for the `--force` flag in the
file (referenced at lines 444-446 and 456) and add or expand the description to
explicitly state that this flag bypasses all queue status checks (published,
cataloged, queued state) and allows models to be re-queued regardless of their
current state. This will help users understand the implications of using the
flag before they invoke it.
scripts/hf-jobs/checkpoint-quant-package-job.sh (1)

259-284: 💤 Low value

Fragile string-based patching of llama.cpp conversion scripts.

The exact-string matching for patching convert_hf_to_gguf.py and utility.py will break if llama.cpp reformats or modifies these lines. The error handling is good (exits if pattern not found), but this could cause unexpected job failures after llama.cpp updates.

Consider documenting this fragility or adding a fallback mechanism. Alternatively, upstream the revision-pinning capability to llama.cpp's converter via a --revision argument.

🤖 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 `@scripts/hf-jobs/checkpoint-quant-package-job.sh` around lines 259 - 284, The
string-based patching logic for modifying convert_hf_to_gguf.py (replacing the
old_snapshot pattern) and utility.py (replacing "/resolve/main/") is fragile and
will break if llama.cpp makes formatting changes. Add documentation in a comment
block above the patching section explaining the brittle nature of these
exact-string matches and what could cause failures. Additionally, enhance the
error handling by capturing the state before patching and adding validation
after each file modification to confirm the expected changes were applied
successfully, rather than relying solely on initial pattern detection.
🤖 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 `@crates/skippy-runtime/src/lib.rs`:
- Around line 3028-3142: The function decode_step_frame_batch_sampled_raw
contains multiple distinct responsibilities including pointer preparation,
buffer allocation, FFI retry handling, token-count updates, and output assembly.
Extract these phases into separate helper functions such as one for preparing
input pointers and buffers, another for handling the FFI call with retry logic,
and another for assembling the output. Replace the verbose inline code in
decode_step_frame_batch_sampled_raw with calls to these focused helpers to
reduce the function length and improve maintainability while staying within code
style guidelines.

In `@crates/skippy-server/src/binary_transport.rs`:
- Around line 884-898: The batched decode candidate check and execution logic in
handle_binary_connection is contributing to binary_transport.rs exceeding 3,000
lines. Extract the is_decode_frame_batch_candidate predicate and the entire
batched decode execution block (including the decode_batcher.decode call and the
assignment of runtime_lock_wait_ms, runtime_lock_hold_ms, runtime_lock_acquires,
decode_batch_size, and decode_batch_wait_ms) into a focused helper method in the
decode_batcher module or a new binary-decode helper module. Replace the inline
conditional block with a single call to this extracted method, allowing
handle_binary_connection to only orchestrate the batched decode path rather than
containing the full implementation details.

In `@crates/skippy-server/src/binary_transport/decode_batcher.rs`:
- Around line 130-145: The wait_for_batch function currently drains frames by
count without validating session uniqueness, which can result in multiple frames
for the same session_id being in a single batch and causing validation failures.
Modify the draining logic in wait_for_batch to track which session_ids have
already been included in the current batch using a set, then iterate through the
pending frames and only add frames whose session_id hasn't been seen yet,
stopping when the batch reaches max_batch_size or when no more unique sessions
are available, leaving frames with duplicate session_ids in the queue for the
next batch.
- Around line 191-201: In the batch processing block where
batch.into_iter().zip(outputs) is called, add validation to ensure the number of
outputs matches the number of pending requests before zipping them together.
Check that outputs.len() equals batch.len(), and if they don't match, send an
error response to all pending items in the batch rather than proceeding with the
zip operation, which would silently drop mismatched items and cause callers to
receive misleading "batcher stopped" errors instead of the actual integration
failure.

In `@crates/skippy-server/src/frontend/decode_batcher.rs`:
- Around line 184-193: The `zip(predicted)` operation in the batch processing
loop silently ignores count mismatches between batch and predicted results,
potentially leaving callers without responses. Before the for loop that iterates
through batch.into_iter().zip(predicted), add an explicit validation check to
ensure predicted.len() equals batch.len(). If the counts don't match, send error
responses to all pending callers via pending.reply.send with an appropriate
error indicating the mismatch, rather than allowing the zip operation to
silently truncate or drop results.

In `@crates/skippy-server/src/frontend/local_generation.rs`:
- Around line 415-445: Extract the batched decode logic from the
generate_local_tokens method into a separate helper function. Create a new
private helper method that encapsulates the decode step starting from the
decode_batcher.decode() call through the signal and runtime session statistics
gathering. This helper should accept the session_id, current token, and request
sampling parameters, then return a struct containing the predicted token, all
collected metrics (batch_size, batch_wait_ms, runtime_lock timings, decode_ms),
and the signal information. Replace the inline decode block in
generate_local_tokens with a call to this new helper method to reduce the
complexity and line count of the main generation loop.

In `@third_party/llama.cpp/patches/0083-Add-skippy-batched-decode-ABI.patch`:
- Around line 192-208: The validation loop that processes each session to build
the batch does not check for duplicate sessions. Before or at the beginning of
the loop that starts with `for (int32_t i = 0; i < n_tokens; ++i)`, add a
validation check to detect if the same skippy_session pointer appears more than
once in the sessions array. If duplicates are found, call llama_batch_free on
the batch, use skippy_set_error to report the error with an appropriate status
code, and return early to prevent corrupting session state by processing the
same session multiple times in a single decode operation.

In
`@third_party/llama.cpp/patches/0084-Add-skippy-batched-activation-decode-ABI.patch`:
- Around line 104-137: The batched activation decode loop starting with the
iteration over n_tokens does not check for duplicate sessions in the sessions
array. Add validation to reject cases where the same session object appears
multiple times in the batch, which would cause it to be processed twice at the
same position. Implement this by comparing each session pointer against all
previously processed sessions in the loop and call skippy_set_error with
SKIPPY_STATUS_INVALID_ARGUMENT and an appropriate message if a duplicate is
found, then return the error status.
- Around line 141-154: Add a guard condition in the activation embedding block
to check if n_embd_inp is less than n_embd and return SKIPPY_STATUS_UNSUPPORTED
before attempting any memory operations. This check should be placed after the
existing SKIPPY_ACTIVATION_FLAG_GEMMA3N_ALTUP flag check and before the
conditional logic that handles the memcpy and memset operations, since the
current else clause assumes n_embd_inp >= n_embd and will cause buffer overflow
on the memcpy call and integer underflow on the memset size calculation when
this assumption is violated.

---

Outside diff comments:
In `@crates/skippy-model-package/src/main.rs`:
- Around line 481-659: The main.rs file exceeds the 2,000 line limit and needs
to be refactored by extracting progress-related code into a dedicated module.
Move the write_package function along with its helper functions
artifact_progress_detail and projector_progress_detail into a new module file
(e.g., package_progress.rs or package_writer.rs). Update the main.rs file to
import and re-export these functions from the new module, ensuring all necessary
dependencies and type imports are included in the new module. This will reduce
the main.rs file size and separate concerns by consolidating progress
orchestration logic into its own responsibility-focused module.

In `@crates/skippy-server/src/frontend.rs`:
- Around line 172-195: The frontend.rs file exceeds the 2,000-line limit per
coding guidelines, and the batcher construction and backend initialization logic
(DecodeBatcher, DecodeFrameBatcher instantiation, and StageOpenAiBackend struct
initialization) is adding to this violation. Extract the backend construction
logic including the creation of decode_batcher, decode_frame_batcher, and the
entire StageOpenAiBackend struct assembly into a separate focused helper
function or dedicated module. This will keep frontend.rs as a
routing/orchestration-only file and reduce its overall line count to comply with
the guidelines. Apply the same refactoring principle to any other similar
constructions mentioned at lines 522-544 and 582-583.

---

Nitpick comments:
In `@crates/model-package/src/bin/queue-unsloth-layer-packages.rs`:
- Around line 444-446: The help text for the `--force` flag does not adequately
explain its behavior to users. Locate the help text definition for the `--force`
flag in the file (referenced at lines 444-446 and 456) and add or expand the
description to explicitly state that this flag bypasses all queue status checks
(published, cataloged, queued state) and allows models to be re-queued
regardless of their current state. This will help users understand the
implications of using the flag before they invoke it.

In `@crates/skippy-server/src/runtime_state.rs`:
- Around line 94-105: The structs RuntimeDecodeBatchRequest and
RuntimeDecodeFrameBatchRequest do not derive Debug, which is inconsistent with
other public structs in the file like RuntimeSessionLaneStats and
RuntimeSessionDropStats. Add the #[derive(Debug)] attribute to both struct
definitions to enable diagnostic logging and debugging without any performance
cost.

In `@scripts/hf-jobs/checkpoint-quant-package-job.sh`:
- Around line 259-284: The string-based patching logic for modifying
convert_hf_to_gguf.py (replacing the old_snapshot pattern) and utility.py
(replacing "/resolve/main/") is fragile and will break if llama.cpp makes
formatting changes. Add documentation in a comment block above the patching
section explaining the brittle nature of these exact-string matches and what
could cause failures. Additionally, enhance the error handling by capturing the
state before patching and adding validation after each file modification to
confirm the expected changes were applied successfully, rather than relying
solely on initial pattern detection.
🪄 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 Plus

Run ID: 59167f13-7030-431c-9895-d862521b0f26

📥 Commits

Reviewing files that changed from the base of the PR and between a6d023c and 623f88b.

📒 Files selected for processing (18)
  • crates/model-package/src/bin/queue-unsloth-layer-packages.rs
  • crates/skippy-ffi/src/lib.rs
  • crates/skippy-model-package/src/main.rs
  • crates/skippy-model-package/src/progress.rs
  • crates/skippy-runtime/src/lib.rs
  • crates/skippy-server/src/binary_transport.rs
  • crates/skippy-server/src/binary_transport/decode_batcher.rs
  • crates/skippy-server/src/frontend.rs
  • crates/skippy-server/src/frontend/decode_batcher.rs
  • crates/skippy-server/src/frontend/embedded_generation.rs
  • crates/skippy-server/src/frontend/generation_flow.rs
  • crates/skippy-server/src/frontend/local_generation.rs
  • crates/skippy-server/src/frontend/tests.rs
  • crates/skippy-server/src/runtime_state.rs
  • scripts/hf-jobs/checkpoint-quant-package-job.sh
  • scripts/hf-jobs/launch-jianyang-phase1.sh
  • third_party/llama.cpp/patches/0083-Add-skippy-batched-decode-ABI.patch
  • third_party/llama.cpp/patches/0084-Add-skippy-batched-activation-decode-ABI.patch

Comment thread crates/skippy-runtime/src/lib.rs Outdated
Comment on lines +884 to +898
let result = if is_decode_frame_batch_candidate(&message, executable_token_ids) {
let token_id = executable_token_ids
.first()
.copied()
.unwrap_or(message.state.current_token);
let sampling = runtime_sampling_config(message.sampling.as_ref());
let outcome = decode_frame_batcher
.decode(&session_key, token_id, sampling.as_ref(), input)
.context("execute batched binary decode frame")?;
runtime_lock_wait_ms = outcome.runtime_lock_wait_ms;
runtime_lock_hold_ms = outcome.runtime_lock_hold_ms;
runtime_lock_acquires = 1;
decode_batch_size = outcome.batch_size;
decode_batch_wait_ms = outcome.batch_wait_ms;
(outcome.predicted, Vec::new(), outcome.output)

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.

🛠️ Refactor suggestion | 🟠 Major | 🏗️ Heavy lift

Move the batch-candidate and execution glue out of this oversized module.

This file is already over 3,000 lines, and the new decode batching branch/helper adds more logic to handle_binary_connection. Please move the candidate predicate plus batched decode execution into decode_batcher or a focused binary-decode helper so this file only orchestrates the call. As per coding guidelines, “Do not add Rust source files over 2,000 lines” and “Do not add Rust methods or functions over the configured Clippy line-count limit.”

Also applies to: 3305-3315

🤖 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-server/src/binary_transport.rs` around lines 884 - 898, The
batched decode candidate check and execution logic in handle_binary_connection
is contributing to binary_transport.rs exceeding 3,000 lines. Extract the
is_decode_frame_batch_candidate predicate and the entire batched decode
execution block (including the decode_batcher.decode call and the assignment of
runtime_lock_wait_ms, runtime_lock_hold_ms, runtime_lock_acquires,
decode_batch_size, and decode_batch_wait_ms) into a focused helper method in the
decode_batcher module or a new binary-decode helper module. Replace the inline
conditional block with a single call to this extracted method, allowing
handle_binary_connection to only orchestrate the batched decode path rather than
containing the full implementation details.

Source: Coding guidelines

Comment on lines +130 to +145
fn wait_for_batch(&self) -> Option<Vec<PendingDecodeFrame>> {
let mut state = self
.state
.lock()
.expect("decode frame batcher lock poisoned");
while state.pending.is_empty() && !state.stopping {
state = self
.ready
.wait(state)
.expect("decode frame batcher lock poisoned");
}
if state.pending.is_empty() && state.stopping {
return None;
}
let batch_size = self.max_batch_size.min(state.pending.len());
Some(state.pending.drain(..batch_size).collect())

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.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Keep duplicate sessions out of a single runtime batch.

RuntimeState batch decode validates unique sessions; draining by count can batch two frames for the same wire session_id, causing the whole batch—including unrelated requests—to fail. Leave later requests for an already-selected session in the queue for the next batch.

Suggested direction
 use std::{
-    collections::VecDeque,
+    collections::{HashSet, VecDeque},
     sync::{
-        let batch_size = self.max_batch_size.min(state.pending.len());
-        Some(state.pending.drain(..batch_size).collect())
+        let mut seen_sessions = HashSet::new();
+        let mut batch = Vec::with_capacity(self.max_batch_size.min(state.pending.len()));
+        let mut deferred = VecDeque::new();
+
+        while let Some(pending) = state.pending.pop_front() {
+            if batch.len() < self.max_batch_size && seen_sessions.insert(pending.session_id.clone())
+            {
+                batch.push(pending);
+                if batch.len() == self.max_batch_size {
+                    break;
+                }
+            } else {
+                deferred.push_back(pending);
+            }
+        }
+
+        deferred.append(&mut state.pending);
+        state.pending = deferred;
+        Some(batch)
🤖 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-server/src/binary_transport/decode_batcher.rs` around lines 130
- 145, The wait_for_batch function currently drains frames by count without
validating session uniqueness, which can result in multiple frames for the same
session_id being in a single batch and causing validation failures. Modify the
draining logic in wait_for_batch to track which session_ids have already been
included in the current batch using a set, then iterate through the pending
frames and only add frames whose session_id hasn't been seen yet, stopping when
the batch reaches max_batch_size or when no more unique sessions are available,
leaving frames with duplicate session_ids in the queue for the next batch.

Comment thread crates/skippy-server/src/binary_transport/decode_batcher.rs Outdated
Comment thread crates/skippy-server/src/frontend/decode_batcher.rs Outdated
Comment thread crates/skippy-server/src/frontend/local_generation.rs Outdated
Comment on lines +192 to +208
+ for (int32_t i = 0; i < n_tokens; ++i) {
+ skippy_session * session = sessions[i];
+ if (session == nullptr || session->ctx != first->ctx || session->stage_model != first->stage_model) {
+ llama_batch_free(batch);
+ skippy_set_error(out_error, SKIPPY_STATUS_INVALID_ARGUMENT, "all sessions must belong to the same stage model");
+ return SKIPPY_STATUS_INVALID_ARGUMENT;
+ }
+ if (skippy_is_filtered(session) && session->stage_model->config.layer_start > 0) {
+ llama_batch_free(batch);
+ skippy_set_error(out_error, SKIPPY_STATUS_UNSUPPORTED, "batched token decode requires the first runtime slice or a full model");
+ return SKIPPY_STATUS_UNSUPPORTED;
+ }
+ batch.token[i] = token_ids[i];
+ batch.pos[i] = session->n_past;
+ batch.n_seq_id[i] = 1;
+ batch.seq_id[i][0] = session->seq_id;
+ batch.logits[i] = 1;

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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reject duplicate sessions before building the native batch.

A caller can pass the same skippy_session* more than once. That batches duplicate seq_id/n_past entries and then advances the same session multiple times after one llama_decode, corrupting the session/KV state.

Proposed guard
     for (int32_t i = 0; i < n_tokens; ++i) {
         skippy_session * session = sessions[i];
         if (session == nullptr || session->ctx != first->ctx || session->stage_model != first->stage_model) {
             llama_batch_free(batch);
             skippy_set_error(out_error, SKIPPY_STATUS_INVALID_ARGUMENT, "all sessions must belong to the same stage model");
             return SKIPPY_STATUS_INVALID_ARGUMENT;
         }
+        for (int32_t j = 0; j < i; ++j) {
+            if (sessions[j] == session) {
+                llama_batch_free(batch);
+                skippy_set_error(out_error, SKIPPY_STATUS_INVALID_ARGUMENT, "batched decode sessions must be unique");
+                return SKIPPY_STATUS_INVALID_ARGUMENT;
+            }
+        }
         if (skippy_is_filtered(session) && session->stage_model->config.layer_start > 0) {
             llama_batch_free(batch);
             skippy_set_error(out_error, SKIPPY_STATUS_UNSUPPORTED, "batched token decode requires the first runtime slice or a full model");
             return SKIPPY_STATUS_UNSUPPORTED;
         }
🤖 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 `@third_party/llama.cpp/patches/0083-Add-skippy-batched-decode-ABI.patch`
around lines 192 - 208, The validation loop that processes each session to build
the batch does not check for duplicate sessions. Before or at the beginning of
the loop that starts with `for (int32_t i = 0; i < n_tokens; ++i)`, add a
validation check to detect if the same skippy_session pointer appears more than
once in the sessions array. If duplicates are found, call llama_batch_free on
the batch, use skippy_set_error to report the error with an appropriate status
code, and return early to prevent corrupting session state by processing the
same session multiple times in a single decode operation.

Comment on lines +104 to +137
+ const size_t hidden_bytes_per_request = skippy_activation_hidden_bytes(first, 1);
+ for (int32_t i = 0; i < n_tokens; ++i) {
+ skippy_session * session = sessions[i];
+ if (session == nullptr || session->ctx != first->ctx || session->stage_model != first->stage_model) {
+ skippy_set_error(out_error, SKIPPY_STATUS_INVALID_ARGUMENT, "all sessions must belong to the same stage model");
+ return SKIPPY_STATUS_INVALID_ARGUMENT;
+ }
+ const skippy_activation_desc * input_desc = input_descs != nullptr ? input_descs[i] : nullptr;
+ const void * input_payload = input_payloads != nullptr ? input_payloads[i] : nullptr;
+ enum skippy_status status = skippy_validate_frame_input(session, input_desc, input_payload, 1, out_error);
+ if (status != SKIPPY_STATUS_OK) {
+ return status;
+ }
+ if (input_desc != nullptr && input_desc->flags != 0) {
+ skippy_set_error(out_error, SKIPPY_STATUS_UNSUPPORTED, "batched activation decode does not support activation sidebands yet");
+ return SKIPPY_STATUS_UNSUPPORTED;
+ }
+ if (skippy_output_activation_flags(session, input_desc) != 0) {
+ skippy_set_error(out_error, SKIPPY_STATUS_UNSUPPORTED, "batched activation decode does not support output activation sidebands yet");
+ return SKIPPY_STATUS_UNSUPPORTED;
+ }
+ status = skippy_prepare_output_activation_frame(
+ session,
+ 1,
+ output_payloads != nullptr ? output_payloads[i] : nullptr,
+ output_payload_capacities != nullptr ? output_payload_capacities[i] : 0,
+ out_output_payload_bytes != nullptr ? &out_output_payload_bytes[i] : nullptr,
+ output_descs != nullptr ? &output_descs[i] : nullptr,
+ input_desc,
+ out_error);
+ if (status != SKIPPY_STATUS_OK) {
+ return status;
+ }
+ }

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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reject duplicate sessions in frame batches too.

This entrypoint has the same duplicate-session hazard: the same session can be decoded twice at the same position in one llama_batch, then advanced twice after a single native decode.

Proposed guard
     for (int32_t i = 0; i < n_tokens; ++i) {
         skippy_session * session = sessions[i];
         if (session == nullptr || session->ctx != first->ctx || session->stage_model != first->stage_model) {
             skippy_set_error(out_error, SKIPPY_STATUS_INVALID_ARGUMENT, "all sessions must belong to the same stage model");
             return SKIPPY_STATUS_INVALID_ARGUMENT;
         }
+        for (int32_t j = 0; j < i; ++j) {
+            if (sessions[j] == session) {
+                skippy_set_error(out_error, SKIPPY_STATUS_INVALID_ARGUMENT, "batched frame decode sessions must be unique");
+                return SKIPPY_STATUS_INVALID_ARGUMENT;
+            }
+        }
         const skippy_activation_desc * input_desc = input_descs != nullptr ? input_descs[i] : nullptr;
         const void * input_payload = input_payloads != nullptr ? input_payloads[i] : nullptr;
🤖 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
`@third_party/llama.cpp/patches/0084-Add-skippy-batched-activation-decode-ABI.patch`
around lines 104 - 137, The batched activation decode loop starting with the
iteration over n_tokens does not check for duplicate sessions in the sessions
array. Add validation to reject cases where the same session object appears
multiple times in the batch, which would cause it to be processed twice at the
same position. Implement this by comparing each session pointer against all
previously processed sessions in the loop and call skippy_set_error with
SKIPPY_STATUS_INVALID_ARGUMENT and an appropriate message if a duplicate is
found, then return the error status.

Comment thread third_party/llama.cpp/patches/0084-Add-skippy-batched-activation-decode-ABI.patch Outdated

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/skippy-model-package/src/main.rs (1)

505-659: 🛠️ Refactor suggestion | 🟠 Major | 🏗️ Heavy lift

Split write_package progress orchestration out of main.rs to meet the file-size constraint.

This adds more logic to a file that is already over 2,000 lines. Please move the write_package progress wiring (including related detail helpers) into an owning module so new behavior is added outside this oversized file.

As per coding guidelines, "Do not add Rust source files over 2,000 lines. If a file is approaching that size, split it by responsibility into an owning module instead of adding more code to the oversized 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 `@crates/skippy-model-package/src/main.rs` around lines 505 - 659, The
write_package function and its helper functions artifact_progress_detail and
projector_progress_detail are contributing to main.rs exceeding the 2,000 line
limit. Extract the entire progress orchestration logic (the block starting with
let mut progress = PackageProgress::new through the final println and Ok(()))
along with both helper functions into a new dedicated module file, then update
main.rs to import and call this extracted function instead. Ensure the extracted
function maintains the same signature and return type as before.

Source: Coding guidelines

🤖 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.

Outside diff comments:
In `@crates/skippy-model-package/src/main.rs`:
- Around line 505-659: The write_package function and its helper functions
artifact_progress_detail and projector_progress_detail are contributing to
main.rs exceeding the 2,000 line limit. Extract the entire progress
orchestration logic (the block starting with let mut progress =
PackageProgress::new through the final println and Ok(())) along with both
helper functions into a new dedicated module file, then update main.rs to import
and call this extracted function instead. Ensure the extracted function
maintains the same signature and return type as before.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: a66f2767-5084-4843-b938-23f8f7dc9041

📥 Commits

Reviewing files that changed from the base of the PR and between ca6e8a4 and e0ba732.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (3)
  • crates/skippy-model-package/Cargo.toml
  • crates/skippy-model-package/src/main.rs
  • crates/skippy-model-package/src/progress.rs

@i386
i386 merged commit f105d3e into main Jun 17, 2026
32 checks passed
@i386
i386 deleted the feat/jianyang branch June 17, 2026 01:45
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.

2 participants