-
Notifications
You must be signed in to change notification settings - Fork 70
Expand file tree
/
Copy pathpre-commit
More file actions
executable file
·257 lines (239 loc) · 12.7 KB
/
Copy pathpre-commit
File metadata and controls
executable file
·257 lines (239 loc) · 12.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
#!/usr/bin/env bash
# pre-commit hook: fast-track gates every commit + heavy gates only when
# the relevant feature is touched.
#
# Install with:
# git config core.hooksPath .githooks
#
# Always (when the staged diff touches kernel/forward-pass/dispatch code):
#
# 1) Speed gate (~30s): prefill/decode tok/s must stay within tolerance
# of the committed ground-floor baselines. Failure = perf regression.
#
# Path-gated heavy gates (only fire when the matching feature is touched):
#
# Do NOT bypass with --no-verify unless you have a plan to fix in the same
# commit.
set -e
# List files staged for this commit.
CHANGED=$(git diff --cached --name-only --diff-filter=ACM)
# ── fmt-bomb backstop (every commit, GPU-free, ~instant) ────────────────
# Catch a workspace-wide reformat BEFORE it lands, whatever produced it —
# `cargo fmt`, an IDE format-on-save, a non-Claude agent, muscle memory.
# Bypass (deliberate reviewed sweep): SKIP_FMT_BOMB_CHECK=1 git commit ...
"$(git rev-parse --show-toplevel)/scripts/check-fmt-bomb.sh" || exit 1
# ── rustfmt-changed gate (every commit, GPU-free, ~instant) ─────────────
# Mirror CI's changed-file rustfmt check (scripts/ci-rustfmt-changed.sh) on
# the staged Rust files. The repo carries historical format debt so we only
# check files in THIS commit, not the workspace. Fix with:
# scripts/fmt-changed.sh <master|beta> # the ref THIS branch was cut from
# Bypass (e.g. mid-rebase) with: SKIP_FMT_CHECK=1 git commit ...
if [[ -z "${SKIP_FMT_CHECK:-}" ]]; then
mapfile -t fmt_files < <(echo "$CHANGED" | grep -E '\.rs$' || true)
if [[ "${#fmt_files[@]}" -gt 0 ]]; then
if ! rustfmt --edition 2021 --check --config skip_children=true "${fmt_files[@]}"; then
echo "" >&2
echo "✗ rustfmt: staged Rust file(s) are not formatted (CI's changed-file gate will flag this)." >&2
echo " Fix: scripts/fmt-changed.sh <master|beta> && git add -u" >&2
echo " Bypass: SKIP_FMT_CHECK=1 git commit ..." >&2
echo " Do NOT run 'cargo fmt' — it reformats the whole workspace's format debt." >&2
exit 1
fi
fi
fi
# Hotspots that can affect forward-pass output or speed.
#
# Updated for post-0.1.20 modular topology (PR 7+):
# - File-name patterns (e.g. `qwen35\.rs`) match across the new arch
# crate locations (e.g. `crates/hipfire-arch-qwen35/src/qwen35.rs`).
# - `arch\.rs` matches the Architecture trait + per-arch impls
# (`crates/hipfire-runtime/src/arch.rs`, `crates/hipfire-arch-*/src/arch.rs`).
# - The 4 PR1-4 modules (loop_guard, sampler, prompt_frame, eos_filter)
# are all hot-path; touching any of them affects every model load.
# - `gemma4\.rs` covered for when the gemma branch forward-ports.
# multi_gpu\.rs is listed here so that touching the PP dispatch path
# triggers the coherence battery (same contract as single-GPU forward).
HOTSPOT='\.hip$|gemv|forward|quant|rotate|dispatch|fwht|rmsnorm|fused|mq4|hfq|magnum|gemm|wmma|llama\.rs|qwen35\.rs|qwen35_vl\.rs|deepseek4\.rs|spec_decode\.rs|gemma4\.rs|sampler\.rs|loop_guard\.rs|prompt_frame\.rs|eos_filter\.rs|arch\.rs|multi_gpu\.rs|mtp_head\.rs|mtp_spec\.rs|mtp_compose\.rs|mtp_probe\.rs|examples/daemon\.rs|speculative\.rs'
# Serve recurrent-state machine — files whose changes can leak DeltaNet/KV state
# ACROSS requests (issue #462). These get an extra MULTI-REQUEST serve-output
# gate (serve-loop-gate.sh) that the single-request coherence battery misses.
SERVE_HOTSPOT='examples/daemon\.rs|speculative\.rs'
# Multi-GPU PP hotspots — superset of the HOTSPOT entry above. pp-gate.sh
# runs only when one of these is touched AND 2+ GPUs are visible; otherwise
# it skips (CI / single-GPU dev box).
PP_HOTSPOT='multi_gpu\.rs|peer_access|pp_|pipeline|stages|forward_prefill_batch_multi|forward_scratch_multi|Gpus|init_uniform|init_layers|boundary_copy'
PP_TOUCHED=0
if echo "$CHANGED" | grep -qE "$PP_HOTSPOT"; then
PP_TOUCHED=1
fi
if ! echo "$CHANGED" | grep -qE "$HOTSPOT"; then
exit 0
fi
# ── bind_thread invariant on dispatch.rs ────────────────────────────────
# Multi-GPU plan (issue #58) requires every pub fn in `impl Gpu` to either
# call `self.bind_thread()` first, or carry a `// bind_thread: skip` marker.
# Silent mis-bind = malloc-on-wrong-device → cross-device pointer corruption.
if echo "$CHANGED" | grep -qE 'crates/rdna-compute/src/dispatch\.rs'; then
if ! ./scripts/verify-bind-thread.sh; then
echo
echo "========================================================================"
echo "COMMIT BLOCKED: dispatch.rs has pub fn missing bind_thread()."
echo "========================================================================"
echo
echo 'Add `self.bind_thread()?;` (or `self.bind_thread_or_warn();` for'
echo 'non-HipResult fn) as the first statement, OR add a'
echo '`// bind_thread: skip — <reason>` comment for pure-state queries.'
echo
exit 1
fi
echo
fi
# ── Multi-request serve OUTPUT gate (issue #462) ─────────────────────────
# Cross-request recurrent-state contamination (DeltaNet/KV state bleeding
# between requests) only shows on a real, multi-request serve. Path-gated to the
# serve state machine (daemon.rs / speculative.rs / cli serve wrapper).
if echo "$CHANGED" | grep -qE "$SERVE_HOTSPOT"; then
echo "=== Serve-loop gate (multi-request output) ==="
echo "Serve/daemon code changed. Firing repeated + distinct greedy requests at"
echo "one warm serve to catch cross-request state contamination (#462)..."
echo
if ! ./scripts/serve-loop-gate.sh; then
echo
echo "========================================================================"
echo "COMMIT BLOCKED: serve-loop gate detected a cross-request attractor."
echo "========================================================================"
echo
echo "A multi-request serve produced a </think> / token-loop attractor —"
echo "recurrent DeltaNet/KV state is leaking across requests (the #462 class,"
echo "which every single-request gate passes). See the per-request output above."
echo
echo " ./scripts/serve-loop-gate.sh # re-run (DFlash path)"
echo " ./scripts/serve-loop-gate.sh --ar # also test the AR path"
echo
exit 1
fi
echo
# qwen2 (arch_id=7) per-request reset regression (#462 bundle class).
# serve-loop-gate above is qwen3.5/DeltaNet-only; this exercises the plain
# qwen2 reset path that the Qwen2Carrier bundle migration broke — the reset
# handler rewound the dead `m.qwen2_state` field, not the live ModelState::
# Qwen2 bundle state, so next_pos bled across requests. Only a real reset
# no-op (exit 1) blocks; SKIP (3, no qwen2 model) / infra (2) warn-only.
if [ -r ./scripts/qwen2-reset-gate.sh ]; then
echo "=== qwen2 per-request reset gate ==="
bash ./scripts/qwen2-reset-gate.sh; q2rc=$?
if [ "$q2rc" -eq 1 ]; then
echo
echo "========================================================================"
echo "COMMIT BLOCKED: qwen2 per-request reset no-op — next_pos bled across requests."
echo "========================================================================"
echo
echo "The daemon reset handler must rewind the live ModelState::Qwen2 bundle"
echo "state via m.qwen2_mut(), not just the dots-ocr-only m.qwen2_state field."
echo " ./scripts/qwen2-reset-gate.sh # re-run"
echo
exit 1
fi
echo
fi
fi
echo
# ── Agentic gate (--fast) ────────────────────────────────────────────────
# Tool-call shape regression battery for A3B variants under realistic-length
# agent system prompts (Pi-style ~1300 tok, Hermes-style ~780 tok). Guards
# against issue #87-class regressions: prompt-length-sensitive corruption of
# tool_call JSON (special-token leak, malformed JSON, stacked openers) that
# coherence-gate.sh's 50-token system prompt cannot reproduce.
#
# Fires on any HOTSPOT match (same surface as coherence-gate). Skips silently
# if A3B models absent (CI-safe). Force full battery via
# HIPFIRE_FORCE_AGENTIC_GATE=1; skip entirely via HIPFIRE_SKIP_AGENTIC_GATE=1.
if [ "${HIPFIRE_SKIP_AGENTIC_GATE:-0}" != "1" ]; then
if [ "${HIPFIRE_FORCE_AGENTIC_GATE:-0}" = "1" ]; then
AGENTIC_FLAG=""
AGENTIC_NOTE="(forced full: 8 cells, ~5 min)"
else
AGENTIC_FLAG="--fast"
AGENTIC_NOTE="(fast: 1 cell, ~2 min — full via HIPFIRE_FORCE_AGENTIC_GATE=1)"
fi
echo "=== Agentic Gate $AGENTIC_NOTE ==="
echo "Tool-call shape regression cover. Skips if A3B models absent."
echo
if ! ./scripts/agentic-gate.sh $AGENTIC_FLAG; then
echo
echo "========================================================================"
echo "COMMIT BLOCKED: agentic-gate.sh hit a HARD failure."
echo "========================================================================"
echo
echo "One or more A3B + agent-prompt cells produced corrupt tool_call output:"
echo "malformed JSON, missing required fields, special-token leak, stacked"
echo "openers, or > 1 soft-warn (collective threshold)."
echo
echo "DO NOT bypass with --no-verify. This regression class silently breaks"
echo "tool-call parsers downstream — see issue #87 (April 2026 auto-MMQ"
echo "regression that ChatML-leaked into <tool_call> JSON)."
echo
echo "Investigation:"
echo " ./scripts/agentic-gate.sh --fast # re-run single cell"
echo " ./scripts/agentic-gate.sh # full 8-cell battery"
echo " ./scripts/agentic-gate.sh --self-check # verify detectors not rotted"
echo
echo "Per-commit escape (use with care): HIPFIRE_SKIP_AGENTIC_GATE=1"
echo
exit 1
fi
echo
fi
# ── Multi-GPU PP gate ────────────────────────────────────────────────────
# Skips silently when fewer than 2 GPU are visible — single-GPU CI and
# dev boxes don't block on multi-GPU regressions. When 2+ GPU AND a
# multi_gpu/pp_/peer_access hotspot is touched, run the parity battery.
if [ "$PP_TOUCHED" -eq 1 ]; then
echo "=== Multi-GPU PP Gate ==="
echo "Multi-GPU code changed. Running pp-gate (skips if <2 GPU)..."
echo
if ! ./scripts/pp-gate.sh; then
echo
echo "========================================================================"
echo "COMMIT BLOCKED: pp-gate.sh hit a HARD failure."
echo "========================================================================"
echo
echo "Multi-GPU pp=2 ≢ pp=1 byte-identical, refusal contract broken,"
echo "or daemon panic. Investigation:"
echo " ./scripts/pp-gate.sh # full battery"
echo " ./scripts/pp-gate.sh --skip-end-to-end # parity-only"
echo
echo "Refer to docs/multi-gpu.md for the validation contract."
exit 1
fi
echo
fi
# ── Speed gate ────────────────────────────────────────────────────────────
echo "=== MQ4 Speed Gate (fast) ==="
echo "Running 4B prefill/decode regression check..."
echo
if ! ./scripts/speed-gate.sh --fast; then
echo
echo "========================================================================"
echo "COMMIT BLOCKED: MQ4 speed regression detected."
echo "========================================================================"
echo
echo "Prefill or decode tok/s dropped below the committed ground-floor baseline."
echo "The current MQ4 numbers are the PERMANENT MINIMUM — no code may ship below."
echo
echo "Next steps:"
echo " ./scripts/speed-gate.sh # all sizes (0.8b / 4b / 9b / 27b)"
echo " ./scripts/speed-gate.sh --verbose # full bench output"
echo
echo "If this regression is INTENTIONAL (new correctness path, new hardware"
echo "target that needs the extra cycles, etc.), justify it AND re-baseline:"
echo " ./scripts/speed-gate.sh --update-baselines"
echo " git add tests/speed-baselines/"
echo " # then re-attempt the commit"
echo
echo "Do NOT bypass with --no-verify without a plan to restore perf in the same commit."
exit 1
fi
echo
echo "=== Both gates passed. Proceeding with commit. ==="
exit 0