diff --git a/npu/forward/include/q4nx_forward.h b/npu/forward/include/q4nx_forward.h index 227344fb7..5ec276cff 100644 --- a/npu/forward/include/q4nx_forward.h +++ b/npu/forward/include/q4nx_forward.h @@ -204,6 +204,9 @@ class Q4nxNpuForward { // Public: a speculative driver outside this class begins a verify batch, then commits a prefix of it. void begin_verify(int rows, int start_pos); bool commit_verify_rows(int accepted); + // How many commits took the full-accept path (kept the batch's own state instead of restoring and replaying it). + // A test that compares the two paths reads it to know the path it meant to test is the one that ran. + long full_accepts_kept() const { return full_accepts_kept_; } bool gdn_apply_rows(int from, int to); // recurrence-only replay of stored rows [from, to) @@ -252,6 +255,7 @@ class Q4nxNpuForward { bool load_norms(); std::vector last_hidden_; // last_hidden(): the post-final-norm state of the last position std::vector last_residual_; // and one norm earlier: the raw residual of the last position + long full_accepts_kept_ = 0; // commit_verify_rows(): see full_accepts_kept() std::vector tap_layers_; // engine#1780: set_layer_taps() std::vector* tap_sink_ = nullptr; size_t tap_base_ = 0; // where the current pass's rows start in *tap_sink_ diff --git a/npu/forward/src/q4nx_forward.cpp b/npu/forward/src/q4nx_forward.cpp index bc7a4e443..494562591 100644 --- a/npu/forward/src/q4nx_forward.cpp +++ b/npu/forward/src/q4nx_forward.cpp @@ -2551,6 +2551,7 @@ bool Q4nxNpuForward::commit_verify_rows(int accepted) { // they are 11 ms + 7 ms per row, 67 ms of an 8-row round. NPU_COMMIT_FULL_REPLAY=1 keeps them, for an A/B. static const bool always_replay = std::getenv("NPU_COMMIT_FULL_REPLAY") != nullptr; const bool keep_batch_state = accepted == verify_rows_ && accepted > 0 && !always_replay && !replay_check; + if (keep_batch_state) full_accepts_kept_++; if (!keep_batch_state) { for (size_t l = 0; l < conv_state_.size(); l++) conv_state_[l] = snap_conv_[l]; for (size_t l = 0; l < rec_state_.size(); l++) rec_state_[l] = snap_rec_[l]; diff --git a/tests/commit_full_accept_test.cpp b/tests/commit_full_accept_test.cpp index 027a9cd0e..83cb524f9 100644 --- a/tests/commit_full_accept_test.cpp +++ b/tests/commit_full_accept_test.cpp @@ -61,6 +61,14 @@ int run_once(const char* dir, const char* out) { if (!any) { std::printf("SKIP: the model has no recurrent (GDN) state\n"); return 77; } } if (!fwd.step(10, pos, logits)) { std::printf("FAIL step after the commit: %s\n", fwd.last_error()); return 1; } + // The negative control: each run must have taken the path it was started for, or the comparison below would + // compare a path with itself and pass while testing nothing. + const bool replay = std::getenv("NPU_COMMIT_FULL_REPLAY") != nullptr; + if (fwd.full_accepts_kept() != (replay ? 0 : 1)) { + std::printf("FAIL the %s run took the other commit path (%ld full accept(s) kept)\n", replay ? "replay" : "kept", + fwd.full_accepts_kept()); + return 1; + } FILE* f = std::fopen(out, "wb"); // file-create-ok: the test's own scratch file beside its build, removed below if (!f) return 1; auto put = [&](const std::vector>& vv) {