Skip to content

feat(dflash): registry-declared draft sidecars — pull fetches, auto uses, on requires - #686

Open
Kaden-Schutt wants to merge 39 commits into
masterfrom
fix/dflash-registry-pairing
Open

feat(dflash): registry-declared draft sidecars — pull fetches, auto uses, on requires#686
Kaden-Schutt wants to merge 39 commits into
masterfrom
fix/dflash-registry-pairing

Conversation

@Kaden-Schutt

Copy link
Copy Markdown
Collaborator

Summary

DFlash was reachable only through an explicit draft path (--model-draft, developer.dflash_draft / HIPFIRE_DFLASH_DRAFT). hipfire pull <tag>-draft set nothing, dflash_mode auto scanned nothing, and the daemon logged "DFlash disabled" only for off — so every user following AGENTS.md / MODELS.md ("pull the draft, set auto, run") was on plain AR without knowing. The filename auto-matcher those docs describe does not exist in the tree.

This PR makes DFlash drafts registry-declared sidecars, exactly like mtp / dspark on ModelEntry: hipfire pull <target> fetches the draft; load_params resolves it for auto/on when no explicit draft is given; on fails closed when it is missing (CLI) or fails to load (daemon Qwen35 arm, mirroring MTP); explicit --model-draft / developer.dflash_draft still win; a final off still drops it. Docs rewritten to describe what the code does. Audit: docs/plans/audit-2026-09-02/audit-Dflash.md (PR #685), Broken 1.

Which crate(s) does this touch?

  • crates/hipfire-registry, registry/models.json + v1.json (new dflash sidecar on 20 dense targets)
  • crates/hipfire-cli (pull/rm sidecar handling; resolve_dflash_sidecar in load_params, re-resolved after the run/bench selector)
  • examples / daemon (SpecLoadCfg.dflash; Qwen35 draft-load Err fails closed when on)
  • docs (AGENTS.md, docs/MODELS.md, docs/env-vars.md)

Evidence (RX 7900 XTX / gfx1100, HIP 7.15, hipx)

Same command, same registry tag, no env var and no --model-draft, prompt benchmarks/prompts/humaneval_3_below_zero.txt (md5 37c5aad9f9efe93b5c47f27256bdf149), --runs 5 --warmups 3 --max-tokens 128 --backend noslots --workload stateless:

binary --spec decode tok/s (median) samples draft
master c66abf3d8 (gate cache) dflash 49.9 [49.7, 49.8, 49.9, 49.9, 49.9] none loaded (drafter=off)
this branch 67d5f268c dflash 202.6 [199.9, 202.3, 202.6, 202.7, 202.8] DFlash draft loaded: qwen38-27b-dflash-mq4.hfq, τ=10.55
this branch 67d5f268c off 49.6 [49.4, 49.5, 49.6, 49.6, 49.7]

Decoded output eyeballed via hipfire run --spec dflash --max-tokens 3000 on the same prompt: correct below_zero implementation after the think span (437 tokens, τ=5.15 with thinking on). Not a token attractor.

Registry note: the branch's bundled registry/v1.json carries the field; the CLI prefers the remote master/registry/v1.json (24 h cache), so the branch was measured with HIPFIRE_NO_REGISTRY_FETCH=1. After merge, users pick the field up on cache expiry or the next hipfire pull.

Artifacts: qwen3.8-27b.mq4 and qwen38-27b-dflash-mq4.hfq (sha256 d0a74a232a…, 1209603072 B, matches the sidecar entry).

de6677ffaa925417c65070adfff31246  /home/kaden/slice-target/release/hipfire
ec311a5b388a68a95dc7af0d2765f5d2  /home/kaden/slice-target/release/daemon
f95ecf031a39ff2ef4da75babd4e6bbe  /home/kaden/actions-runner/_cache/hw-gate-target/release/hipfire
ea979d509c0782d4284212415ecb3143  /home/kaden/actions-runner/_cache/hw-gate-target/release/daemon
2fb2edc27865b697c5209c104de91f32  /home/kaden/.hipfire/models/qwen3.8-27b.mq4
bench JSON — this branch, --spec dflash
{
 "protocol": "native-generate-v1",
 "model": "qwen3.8:27b",
 "loaded": {
  "type": "loaded",
  "arch": "qwen3_5",
  "dim": 5120,
  "layers": 64,
  "vocab": 248320,
  "vl": false,
  "reasoning_contract": "qwen_jinja",
  "reasoning_effort_native": true,
  "reasoning_efforts": [
   "low",
   "medium",
   "xhigh"
  ],
  "cache_capable": true,
  "retry_reset_eligible": true,
  "continuous_batch_capable": false
 },
 "gpu": {
  "type": "diag",
  "arch": "gfx1100",
  "hip_version": "7.15",
  "vram_free_mb": 6722,
  "vram_total_mb": 24560,
  "model_loaded": true,
  "model_arch": "qwen3_5",
  "kernels": 0,
  "kernel_hashes": 0
 },
 "vram_free_before_mb": 24522,
 "max_tokens": 128,
 "runs": 5,
 "batch": 1,
 "decode_tok_s": {
  "median": 202.6,
  "mean": 202.06,
  "min": 199.9,
  "max": 202.8,
  "stdev": 1.092886087385138
 },
 "prefill_tok_s": {
  "median": 386.3,
  "mean": 383.12,
  "min": 369.8,
  "max": 387.0,
  "stdev": 6.666153313568474
 },
 "wall_tok_s": {
  "median": 132.5,
  "mean": 131.92,
  "min": 129.4,
  "max": 132.7,
  "stdev": 1.263962024745993
 },
 "ttft_ms": {
  "median": 334.0,
  "mean": 336.84,
  "min": 333.3,
  "max": 348.9,
  "stdev": 6.036091450599459
 },
 "samples": {
  "decode": [
   199.9,
   202.3,
   202.6,
   202.7,
   202.8
  ],
  "prefill": [
   369.8,
   386.2,
   386.3,
   387.0,
   386.3
  ],
  "wall": [
   129.4,
   132.4,
   132.5,
   132.7,
   132.6
  ],
  "ttft_ms": [
   348.9,
   334.0,
   334.0,
   333.3,
   334.0
  ]
 }
}
bench JSON — master, --spec dflash (AR)
{
 "protocol": "native-generate-v1",
 "model": "qwen3.8:27b",
 "loaded": {
  "type": "loaded",
  "arch": "qwen3_5",
  "dim": 5120,
  "layers": 64,
  "vocab": 248320,
  "vl": false,
  "reasoning_contract": "qwen_jinja",
  "reasoning_effort_native": true,
  "reasoning_efforts": [
   "low",
   "medium",
   "xhigh"
  ],
  "cache_capable": true,
  "retry_reset_eligible": true,
  "continuous_batch_capable": false
 },
 "gpu": {
  "type": "diag",
  "arch": "gfx1100",
  "hip_version": "7.15",
  "vram_free_mb": 8620,
  "vram_total_mb": 24560,
  "model_loaded": true,
  "model_arch": "qwen3_5",
  "kernels": 0,
  "kernel_hashes": 0
 },
 "vram_free_before_mb": 24522,
 "max_tokens": 128,
 "runs": 5,
 "batch": 1,
 "decode_tok_s": {
  "median": 49.9,
  "mean": 49.84,
  "min": 49.7,
  "max": 49.9,
  "stdev": 0.07999999999999864
 },
 "prefill_tok_s": {
  "median": 421.5,
  "mean": 420.43999999999994,
  "min": 411.8,
  "max": 424.6,
  "stdev": 4.5071498754756325
 },
 "wall_tok_s": {
  "median": 44.5,
  "mean": 44.5,
  "min": 44.3,
  "max": 44.6,
  "stdev": 0.10954451150103478
 },
 "ttft_ms": {
  "median": 306.1,
  "mean": 306.84000000000003,
  "min": 303.8,
  "max": 313.2,
  "stdev": 3.316986584235752
 },
 "samples": {
  "decode": [
   49.7,
   49.8,
   49.9,
   49.9,
   49.9
  ],
  "prefill": [
   411.8,
   421.5,
   424.6,
   423.3,
   421.0
  ],
  "wall": [
   44.3,
   44.5,
   44.6,
   44.6,
   44.5
  ],
  "ttft_ms": [
   313.2,
   306.1,
   303.8,
   304.7,
   306.4
  ]
 }
}

Test plan

Not in this PR

  • Draft-target identity checks (audit Broken 6), VRAM accounting for the draft (Broken 2) — separate slices.
  • The llama-carrier (arch 0/1) draft fallbacks stay log-and-AR; failing them closed needs bundle rollback plumbing.

@Kaden-Schutt

Copy link
Copy Markdown
Collaborator Author

Session evidence (serve path, growing context) — and two fixes it forced

Ran the committed 8-turn coding session (benchmarks/prompts/session_coding.json, md5 c0d470288bde3f1e54e4bba04da8f8a2) through scripts/serve_harness.py --mode session, greedy, thinking off, q8 KV, on the RX 7900 XTX, qwen3.8:27b-mq5 + its mq5 draft vs AR.

DFlash arm — dies at turn 3:

t1  ctx=51    cached=0  gen=3020  decode=46.2 tok/s  tau=3.60
t2  ctx=3126  cached=0  gen=1747  decode=33.0 tok/s  tau=3.49
t3  spec_step: HipError(2): hipMemCreate: out of memory   (ctx ≈ 4.9k)
t4–t8  empty responses

AR arm — all 8 turns, recall 3/3:

t1 ctx=51    38.1 tok/s
t3 ctx=5240  36.7
t5 ctx=10527 35.2
t8 ctx=13371 34.4      VRAM 24.2 → 24.6 GB, released after

KV is 68 KiB/token here (16 KV layers × 8 heads × 2 × 272 B), so 13k ctx is 0.9 GB; the card is tight because of 18.7 GB weights + ~5 GB fixed non-weight residency. Adding a 1.7 GB draft leaves nothing for VMM to grow into, and the failure is silent to the user (empty turns), which is exactly the audit's Broken 2 (draft VRAM charged nowhere).

Consequences for this PR:

  1. 99c9c7f25 — the dflash sidecar is now declared only where the 24 GB budget closes: mq4-tier and below (measured 202 tok/s on qwen3.8:27b). Dropped from qwen3.8:27b-mq5/-pro/-xt, -mq6/-pro/-xt, and qwen3.5:27b-mq6. Users can still pin those drafts explicitly.
  2. e9ce75527 — sidecar lookup now searches the models dir first: find_model_path canonicalizes, so a target that is a symlink (every ladder artifact on the bench boxes) had a parent with no draft in it. Regression test with a symlinked target.
  3. 199c2d222serve reports a path-requested model by its path again. Resolving path→entry had made /health.model the tag, which broke serve_harness's warm probe (it realpath-compares to the launched path) — the harness killed and respawned serve every 180 s.

Two findings that are not this PR's:

  • τ is genre-bound, not path-bound. Standalone hipfire bench on the same mq5 + draft: Python humaneval τ=10.3, the session's Rust function prompt 4.4, the architecture-prose prompt 3.1. The "4× on code" claim holds for Python-shaped prompts; on Rust the draft nets +15–40%. Draft training data.
  • Under spec, cached=0 on turn 2 (the AR arm shows cached=51/2099/4147…): the prompt cache does not carry across speculative turns, so t2 re-prefilled 3.1k tokens and came in below AR. Needs its own look.

@hipfire-sol

hipfire-sol Bot commented Sep 4, 2026

Copy link
Copy Markdown

hw-gate sol prelim

summary: Adds registry-declared DFlash draft sidecars: pulling a target also downloads its draft, run/bench/serve resolve that sidecar for auto/on modes, explicit draft paths retain precedence, on fails closed when the draft is absent or unloadable, and rm preserves a shared draft while another installed target declares it. It also tightens path-to-registry identity using canonical installed paths and drains GPU state after a failed Qwen3.5-family load rollback.

run_hardware: true
run_hardware_reasons: The executable changes are ordinary Rust load/config/filesystem logic confined to registry, model, cache, and temporary paths.; No dependency, toolchain, CI, build-script, unsafe, credential, or process-spawning change is present.; scripts/registry_gen.py performs registry-generation network metadata lookup only when explicitly invoked; this diff merely extends existing sidecar annotation from triattn/mtp to dflash and the hardware route need not execute it.; Real artifacts are required to validate registry pairing, canonical path identity, speculative draft compatibility, and failed-load rollback.

routes:

mode tag source why
battery qwen3.6:27b bucket bucket load
battery ornith-1.5:35b-a3b-mq4r bucket bucket load
battery lfm2.5:1.2b bucket bucket load
battery qwen3.8:27b-mq4-xt bucket bucket load

unavailable_routes:

(none)

claim_assessment: The author claims target pulls acquire registry-declared drafts, auto uses an available draft, on fails closed when missing or unloadable, explicit drafts win, off strips drafts, shared-sidecar removal is safe, and real qwen3.8 DFlash generation improves over AR without incoherence. Proving this requires real-fixture loads showing the intended draft in loader output, coherent decoded battery turns, tag/path parity for the XT fixture, and a failed on-mode load followed by a clean valid load; the supplied benchmark and unit-test reports are claims rather than gate evidence.

@hipfire-sol

hipfire-sol Bot commented Sep 4, 2026

Copy link
Copy Markdown

hw-gate evidence — 2 lane(s) — verdict pass

lane hiptrx (gfx1201)

hw-gate evidence

field value
base cf87b0b1c18c3d54b3d9ff4f3f8ff28b817bae12
head 876cf28d0fa4edaec3c14c8bdd9475be9f8b94e0
buckets load
host gfx gfx1201
host rocm 7.15.26333-0000000
device 3
runner hiptrx
daemon_md5 b0181b8bb8417a3d45f80272b8667b09
hipfire_md5 421171aa7515c54e2fcc93e59ed0bb9d
build_seconds 40.41806650161743
verdict pass
logs_dir hw-gate-logs

fixtures

qwen3.6:27b

source: bucket sha256_ok: ✅ size_ok: ✅ status: pass reason:

battery — exit 0 seconds 31.4 status pass

mode idx genre finish ctx cached gen ans_words prefill_tok_s decode_tok_s attractor empty runaway recall_ok
battery 0 stop 34 0 132 62 3.1 15.0 False False False True
battery 1 stop 30 0 77 66 504.9 36.1 False False False True
battery 2 stop 31 0 19 16 524.6 36.2 False False False True
battery 3 stop 47 0 31 8 561.6 36.1 False False False True
battery 4 stop 47 0 17 7 565.5 36.1 False False False True
qwen3.6:27b battery turn 0
```python
def longest_substring_without_repeating(s: str) -> str:
    char_index = {}
    start = 0
    max_length = 0
    max_start = 0

    for end, char in enumerate(s):
        if char in char_index and char_index[char] >= start:
            start = char_index[char] + 1
        char_index[char] = end
        if end - start + 1 > max_length:
            max_length = end - start + 1
            max_start = start

    return s[max_start:max_start + max_length]
```
qwen3.6:27b battery turn 1
During the day, sunlight scatters off gas molecules in the atmosphere, with shorter blue wavelengths spreading more widely than other colors. However, at sunset, light travels through a thicker layer of the atmosphere, causing most of the blue light to scatter out of our line of sight. This leaves only the longer red and orange wavelengths to reach our eyes, creating the characteristic hues of sunset.
qwen3.6:27b battery turn 2
The capital of France is Paris, and the river that runs through it is the Seine.
qwen3.6:27b battery turn 3
{
  "name": "Alice",
  "age": 34,
  "city": "Lisbon"
}
qwen3.6:27b battery turn 4
17 + 26 = 43

Answer: 43

ornith-1.5:35b-a3b-mq4r

source: bucket sha256_ok: ✅ size_ok: ✅ status: pass reason:

battery — exit 0 seconds 32.7 status pass

mode idx genre finish ctx cached gen ans_words prefill_tok_s decode_tok_s attractor empty runaway recall_ok
battery 0 stop 34 0 128 62 23.4 20.7 False False False True
battery 1 stop 30 0 119 105 450.0 25.2 False False False True
battery 2 stop 31 0 16 13 480.9 80.9 False False False True
battery 3 stop 47 0 22 6 685.5 58.4 False False False True
battery 4 stop 47 0 17 7 689.0 50.5 False False False True
ornith-1.5:35b-a3b-mq4r battery turn 0
```python
def longest_substring_no_repeat(s: str) -> str:
    seen = {}
    start = 0
    best_start = 0
    best_len = 0

    for end, char in enumerate(s):
        if char in seen and seen[char] >= start:
            start = seen[char] + 1
        seen[char] = end

        if end - start + 1 > best_len:
            best_len = end - start + 1
            best_start = start

    return s[best_start:best_start + best_len]
```
ornith-1.5:35b-a3b-mq4r battery turn 1
The sky appears blue during the day because sunlight interacts with gases in Earth's atmosphere through a process called Rayleigh scattering, which preferentially disperses shorter blue wavelengths in all directions, filling the sky with that color. As the sun sets, its light must travel through a much thicker portion of the atmosphere to reach your eyes, scattering away most of the blue light before it arrives and allowing the longer red and orange wavelengths to pass through largely unaffected. This combination of scattered blue light overhead during the day and the reddened, direct sunlight at the horizon explains the two distinct color changes you observe.
ornith-1.5:35b-a3b-mq4r battery turn 2
The capital of France is Paris, and the Seine River runs through it.
ornith-1.5:35b-a3b-mq4r battery turn 3
{"name": "Alice", "age": 34, "city": "Lisbon"}
ornith-1.5:35b-a3b-mq4r battery turn 4
17 + 26 = 43

Answer: 43

lfm2.5:1.2b

source: bucket sha256_ok: ✅ size_ok: ✅ status: pass reason:

battery — exit 0 seconds 8.0 status pass

mode idx genre finish ctx cached gen ans_words prefill_tok_s decode_tok_s attractor empty runaway recall_ok
battery 0 stop 0 0 110 45 None 207.94 False False False True
battery 1 stop 0 0 75 67 None 208.33 False False False True
battery 2 stop 0 0 15 13 None 208.33 False False False True
battery 3 stop 0 0 26 8 None 208.0 False False False True
battery 4 stop 0 0 20 11 None 208.33 False False False True
lfm2.5:1.2b battery turn 0
```python
def longest_unique_substring(s):
    seen = set()
    start = 0
    max_len = 0
    max_sub = ""

    for i in range(len(s)):
        while s[i] in seen:
            seen.remove(s[start])
            start += 1
        seen.add(s[i])
        if i + 1 > max_len:
            max_len = i + 1
            max_sub = s[start:i+1]

    return max_sub
```
lfm2.5:1.2b battery turn 1
The sky appears blue during the day because sunlight scatters off particles in the Earth's atmosphere, with shorter blue wavelengths being dispersed more evenly. As the sun sets, the light travels through a longer path in the atmosphere, causing more scattering of the longer red wavelengths. This shift in scattering and our view of the sun at a lower angle makes the red hues dominate at sunset.
lfm2.5:1.2b battery turn 2
The capital of France is Paris, and the River Seine runs through it.
lfm2.5:1.2b battery turn 3
{
  "name": "Alice",
  "age": 34,
  "city": "Lisbon"
}
lfm2.5:1.2b battery turn 4
Let's calculate the sum: 17 + 26 equals 43.  
Answer: 43

qwen3.8:27b-mq4-xt

source: bucket sha256_ok: ✅ size_ok: ✅ status: pass reason:

battery — exit 0 seconds 12.3 status pass

mode idx genre finish ctx cached gen ans_words prefill_tok_s decode_tok_s attractor empty runaway recall_ok
battery 0 stop 34 0 143 67 54.6 44.7 False False False True
battery 1 stop 30 0 91 80 182.3 61.5 False False False True
battery 2 stop 31 0 16 13 190.9 53.2 False False False True
battery 3 stop 47 0 22 6 261.5 36.4 False False False True
battery 4 stop 47 0 17 7 254.8 67.7 False False False True
qwen3.8:27b-mq4-xt battery turn 0
```python
def longest_substring_without_repeating(s: str) -> str:
    if not s:
        return ""
    
    last_index = {}
    start = 0
    max_length = 0
    max_start = 0
    
    for i, char in enumerate(s):
        if char in last_index and last_index[char] >= start:
            start = last_index[char] + 1
        last_index[char] = i
        if i - start + 1 > max_length:
            max_length = i - start + 1
            max_start = start
    
    return s[max_start:max_start + max_length]
```
qwen3.8:27b-mq4-xt battery turn 1
The sky appears blue during the day because sunlight scatters in the atmosphere, with shorter blue wavelengths scattering much more widely than other colors due to Rayleigh scattering. As the sun approaches the horizon at sunset, sunlight must pass through a thicker layer of the atmosphere, which filters out the shorter blue wavelengths before they reach your eyes. This leaves the longer, less scattered red and orange wavelengths to dominate the sky, creating the characteristic warm colors of a sunset.
qwen3.8:27b-mq4-xt battery turn 2
The capital of France is Paris, and the river Seine runs through it.
qwen3.8:27b-mq4-xt battery turn 3
{"name": "Alice", "age": 34, "city": "Lisbon"}
qwen3.8:27b-mq4-xt battery turn 4
17 + 26 = 43

Answer: 43

kernel

not run

lane hipx (gfx1100)

hw-gate evidence

field value
base cf87b0b1c18c3d54b3d9ff4f3f8ff28b817bae12
head 876cf28d0fa4edaec3c14c8bdd9475be9f8b94e0
buckets load
host gfx gfx1100
host rocm 7.15.26333-0000000
device 0
runner hipx
daemon_md5 b0181b8bb8417a3d45f80272b8667b09
hipfire_md5 421171aa7515c54e2fcc93e59ed0bb9d
build_seconds 45.65575933456421
verdict pass
logs_dir hw-gate-logs

fixtures

qwen3.6:27b

source: bucket sha256_ok: ✅ size_ok: ✅ status: pass reason:

battery — exit 0 seconds 28.5 status pass

mode idx genre finish ctx cached gen ans_words prefill_tok_s decode_tok_s attractor empty runaway recall_ok
battery 0 stop 34 0 132 62 3.2 17.3 False False False True
battery 1 stop 30 0 87 76 440.8 49.8 False False False True
battery 2 stop 31 0 16 13 447.8 49.9 False False False True
battery 3 stop 47 0 31 8 460.7 49.8 False False False True
battery 4 stop 47 0 6 2 464.4 49.9 False False False True
qwen3.6:27b battery turn 0
```python
def longest_substring_without_repeating(s: str) -> str:
    char_index = {}
    start = 0
    max_length = 0
    max_start = 0

    for end, char in enumerate(s):
        if char in char_index and char_index[char] >= start:
            start = char_index[char] + 1
        char_index[char] = end
        if end - start + 1 > max_length:
            max_length = end - start + 1
            max_start = start

    return s[max_start:max_start + max_length]
```
qwen3.6:27b battery turn 1
During the day, sunlight scatters off gas molecules in the atmosphere, with shorter blue wavelengths dispersing more easily than other colors to create a blue sky. At sunset, sunlight must travel through a much thicker layer of the atmosphere to reach your eyes, scattering away most of the blue and green light before it arrives. This leaves only the longer red and orange wavelengths to pass through, resulting in the vibrant red hues seen during sunset.
qwen3.6:27b battery turn 2
The capital of France is Paris, and the River Seine runs through it.
qwen3.6:27b battery turn 3
{
  "name": "Alice",
  "age": 34,
  "city": "Lisbon"
}
qwen3.6:27b battery turn 4
Answer: 43

ornith-1.5:35b-a3b-mq4r

source: bucket sha256_ok: ✅ size_ok: ✅ status: pass reason:

battery — exit 0 seconds 33.6 status pass

mode idx genre finish ctx cached gen ans_words prefill_tok_s decode_tok_s attractor empty runaway recall_ok
battery 0 stop 34 0 129 59 438.4 20.0 False False False True
battery 1 stop 30 0 87 77 478.2 98.1 False False False True
battery 2 stop 31 0 16 13 514.4 82.0 False False False True
battery 3 stop 47 0 36 10 724.2 94.3 False False False True
battery 4 stop 47 0 17 7 648.7 113.1 False False False True
ornith-1.5:35b-a3b-mq4r battery turn 0
```python
def longest_substring_without_repeating_chars(s):
    start = 0
    max_start = 0
    max_length = 0
    char_index = {}
    
    for end, char in enumerate(s):
        if char in char_index and char_index[char] >= start:
            start = char_index[char] + 1
        char_index[char] = end
        if end - start + 1 > max_length:
            max_length = end - start + 1
            max_start = start
    
    return s[max_start:max_start + max_length]
```
ornith-1.5:35b-a3b-mq4r battery turn 1
During the day, the sky appears blue because sunlight interacts with particles in the atmosphere, and shorter blue wavelengths scatter more readily than other colors, filling the sky with this hue. At sunset, the sunlight travels through a thicker portion of the atmosphere to reach your eyes, causing most of the blue light to be scattered away before it arrives. This leaves the longer red and orange wavelengths to dominate, painting the sky with warm sunset colors.
ornith-1.5:35b-a3b-mq4r battery turn 2
The capital of France is Paris, and the Seine River runs through it.
ornith-1.5:35b-a3b-mq4r battery turn 3
```json
{
  "name": "Alice",
  "age": 34,
  "city": "Lisbon"
}
```
ornith-1.5:35b-a3b-mq4r battery turn 4
17 + 26 = 43

Answer: 43

lfm2.5:1.2b

source: bucket sha256_ok: ✅ size_ok: ✅ status: pass reason:

battery — exit 0 seconds 8.1 status pass

mode idx genre finish ctx cached gen ans_words prefill_tok_s decode_tok_s attractor empty runaway recall_ok
battery 0 stop 0 0 129 57 None 224.74 False False False True
battery 1 stop 0 0 59 50 None 226.92 False False False True
battery 2 stop 0 0 15 13 None 227.27 False False False True
battery 3 stop 0 0 26 8 None 228.07 False False False True
battery 4 stop 0 0 20 11 None 227.27 False False False True
lfm2.5:1.2b battery turn 0
```python
def longest_substring_without_repeating(s):
    seen = set()
    start = 0
    max_len = 0
    max_sub = ""
    for i in range(len(s)):
        if s[i] in seen:
            while start < i and s[start] in seen:
                seen.remove(s[start])
                start += 1
        seen.add(s[i])
        if i - start + 1 > max_len:
            max_len = i - start + 1
            max_sub = s[start:i+1]
    return max_sub
```
lfm2.5:1.2b battery turn 1
The sky appears blue during the day because sunlight interacts with the atmosphere, scattering shorter blue wavelengths more effectively. At sunset, the sun is lower in the sky, so the longer red wavelengths remain visible, creating the vivid red color. This phenomenon is known as Rayleigh scattering and twilight effects.
lfm2.5:1.2b battery turn 2
The capital of France is Paris, and the Seine River runs through it.
lfm2.5:1.2b battery turn 3
{
  "name": "Alice",
  "age": 34,
  "city": "Lisbon"
}
lfm2.5:1.2b battery turn 4
Let's calculate the sum: 17 + 26 equals 43.  
Answer: 43

qwen3.8:27b-mq4-xt

source: bucket sha256_ok: ✅ size_ok: ✅ status: pass reason:

battery — exit 0 seconds 18.9 status pass

mode idx genre finish ctx cached gen ans_words prefill_tok_s decode_tok_s attractor empty runaway recall_ok
battery 0 stop 34 0 129 58 311.0 28.2 False False False True
battery 1 stop 30 0 80 70 419.3 48.4 False False False True
battery 2 stop 31 0 16 13 430.4 48.5 False False False True
battery 3 stop 47 0 22 6 476.4 48.5 False False False True
battery 4 stop 47 0 9 3 479.5 48.4 False False False True
qwen3.8:27b-mq4-xt battery turn 0
```python
def longest_substring_without_repeating(s: str) -> str:
    start = 0
    char_index = {}
    best_start = 0
    best_end = 0
    
    for i, ch in enumerate(s):
        if ch in char_index and char_index[ch] >= start:
            start = char_index[ch] + 1
        char_index[ch] = i
        if i - start > best_end - best_start:
            best_start = start
            best_end = i
    
    return s[best_start:best_end + 1]
```
qwen3.8:27b-mq4-xt battery turn 1
Sunlight appears white because it contains all visible colors, but shorter blue wavelengths scatter more easily through the atmosphere than longer red wavelengths. During the day, this scattered blue light reaches our eyes from all directions, making the sky appear blue. At sunset, sunlight travels through a much thicker layer of atmosphere, filtering out most of the blue light and leaving the dominant red and orange hues to be visible.
qwen3.8:27b-mq4-xt battery turn 2
The capital of France is Paris, and the River Seine runs through it.
qwen3.8:27b-mq4-xt battery turn 3
{"name": "Alice", "age": 34, "city": "Lisbon"}
qwen3.8:27b-mq4-xt battery turn 4
43

Answer: 43

kernel

not run

ghazni101 pushed a commit to ghazni101/hipfire that referenced this pull request Sep 4, 2026
… a seat

First live run of the rung (workflow_dispatch on warpfront#686, run 33848923893)
never reached Sol: review.py launched omp with cwd=checkout AND
`--cwd checkout`, and the workflow passes `--checkout pr` relative to the
job workspace, so omp resolved `pr/pr` and exited 1 before reading the
diff. prelim.json came back null, hardware was skipped, and the status
went red with "sol prelim unavailable".

The dry runs on warpfront#679 did not catch it because the fake omp ignored
`--cwd` and every test passed an absolute checkout.

- review.py: abspath the checkout once after parse_args; all three seat
  launches and the git helpers inherit it.
- tests/fake_omp.py: chdir to `--cwd` like the real binary, exit 1 with
  the same error shape when it does not exist.
- tests/test_review.py: prelim with a relative --checkout from the parent
  directory must reach the seat and pass an absolute --cwd. Fails on the
  old script with the runner's exact error; passes with the fix.

103/103 in scripts/hw-gate/tests.
@hipfire-sol

hipfire-sol Bot commented Sep 4, 2026

Copy link
Copy Markdown

hw-gate sol verdict

{
  "claim_verdict": "not-exercised",
  "confidence": 0.97,
  "coverage": {
    "gaps": [
      "The batteries do not show DFlash loader logs, tau, or other evidence that a registry-declared draft was loaded; qwen3.6:27b and qwen3.8:27b-mq4-xt decode rates are consistent with AR.",
      "No tag-versus-installed-path or symlink parity route exercised registry_entry_for_path.",
      "No dflash_mode=on missing or unloadable-draft failure followed by a valid load exercised rollback_unfinished_qwen35 and GPU-pool recovery.",
      "Target pull, shared-sidecar rm behavior, explicit-draft precedence, final off stripping, and registry sidecar digest/download behavior were not exercised by hardware evidence.",
      "registry/models.json and registry/v1.json are policy files and require human review."
    ],
    "surfaces_evidenced": [
      "load",
      "serve"
    ],
    "surfaces_touched": [
      "load",
      "serve",
      "config",
      "registry",
      "filesystem",
      "docs"
    ]
  },
  "decision": "needs-human",
  "eyeball": [
    "All five qwen3.6:27b battery responses on gfx1201 and gfx1100 are coherent, answer their prompts, contain the expected substrings, and show no attractor or special-token leakage.",
    "All five qwen3.8:27b-mq4-xt responses on both lanes are coherent and satisfy their prompts, but the evidence does not establish that its shared DFlash sidecar was active.",
    "The unrelated ornith-1.5:35b-a3b-mq4r and lfm2.5:1.2b batteries are coherent but do not cover the changed DFlash registry behavior."
  ],
  "phase": "verdict",
  "rationale": "The hardware job passed every fixture on gfx1201 and gfx1100 with coherent decoded text, so it reveals no regression. However, the central behavior in crates/hipfire-cli/src/main.rs:2661, crates/hipfire-loader/src/lib.rs:1975, and registry/models.json was not demonstrated: there is no evidence of a registry sidecar loading, on-mode failure and rollback, path/symlink identity parity, pull, or rm semantics. Because the core claim remains unexercised and the PR changes registry policy files, the result requires human review rather than a greenlight.",
  "regressions": []
}

Floor: hard=['policy_paths: registry/models.json,registry/v1.json'] soft=["coverage_gaps: ['The batteries do not show DFlash loader logs, tau, or other evidence that a registry-declared draft was loaded; qwen3.6:27b and qwen3.8:27b-mq4-xt decode rates are consistent with AR.', 'No tag-versus-installed-path or symlink parity route exercised registry_entry_for_path.', 'No dflash_mode=on missing or unloadable-draft failure followed by a valid load exercised rollback_unfinished_qwen35 and GPU-pool recovery.', 'Target pull, shared-sidecar rm behavior, explicit-draft precedence, final off stripping, and registry sidecar digest/download behavior were not exercised by hardware evidence.', 'registry/models.json and registry/v1.json are policy files and require human review.']", 'model needs-human'] model_decision=needs-human final=needs-human

@hipfire-sol hipfire-sol Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

hw-gate sol verdict block: hw-gate.json reports failure for both hiptrx and hipx, null hipfire and daemon MD5s, and no fixture results. Consequently, the sidecar resolution at crates/hipfire-cli/src/main.rs:2590, path-form registry policy at crates/hipfire-registry/src/lib.rs:424, and required-draft rollback at crates/hipfire-loader/src/lib.rs:1965 were not exercised. Author-reported tests and benchmarks are claims rather than gate evidence; the failed hardware run requires block.

@hipfire-sol hipfire-sol Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

hw-gate sol verdict block: The clean hipx batteries show no ordinary-load or generation regression, but all four hiptrx routes failed with exit 1 and no rows. The decision rules require block on any fixture failure. More importantly, the evidence never enabled DFlash: it therefore does not validate sidecar resolution in crates/hipfire-cli/src/main.rs:2590, pull inclusion at :1618, daemon policy lowering in crates/hipfire-daemon/src/main.rs:1338, or fail-closed draft loading in crates/hipfire-loader/src/lib.rs:1965. The author's central pull/auto/on/override and throughput claims remain unexercised.

@hipfire-sol hipfire-sol Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

hw-gate sol verdict needs-human: The generic load changes did not regress the tested fixtures: qwen3.6:27b, qwen3.8:27b-mq4-xt, ornith-1.5:35b-a3b-mq4r, and lfm2.5:1.2b passed coherent batteries on gfx1201 and gfx1100. However, the central behavior added at crates/hipfire-cli/src/main.rs:2598 and crates/hipfire-loader/src/lib.rs:1965 was not observed: the evidence contains no proof that a registry DFlash sidecar loaded, no missing-sidecar on/auto cases, no pull flow, and no absolute-path resolution case for crates/hipfire-registry/src/lib.rs:424. Because registry/models.json and registry/v1.json are policy files and material coverage gaps remain, human review is required.

@hipfire-fable

hipfire-fable Bot commented Sep 4, 2026

Copy link
Copy Markdown

announcement: Holding on the policy floor only: registry/models.json and registry/v1.json changed, and that needs a human to accept the sidecar declarations (and to note that a beta build still fetches master's registry, so the field is inert on staging until it lands there). On hardware the change does what it says: a bare qwen3.6:27b tag under on/auto loads qwen36-27b-dflash-mq4.hfq and decodes coherently with tau up to ~9.7 where the same run on master is plain AR; the symlinked qwen3.8-27b.mq4-xt requested by path gets its entry and sidecar; on with the sidecar missing exits before load, auto warns and runs AR, and an unloadable draft under on is refused with the target's 15 GB actually returned (32404 MB free reported by the daemon, lfm2.5 loads fine on the same process afterwards). One nit: hipfire run prints the 'not pulled; running AR' line twice. Evidence and the exact commands are in the hw-gate evidence artifact.

investigation:

| question | route | result | evidence |

|---|---|---|---|

| Does dflash_mode=on with a registry TAG and no draft flag/env resolve the declared sidecar and actually run DFlash coherently (Sol gap 1)? | HIPFIRE_NO_REGISTRY_FETCH=1 HIP_VISIBLE_DEVICES=0 HIPFIRE_CLI_BIN=$HW_GATE_BIN/hipfire HIPFIRE_DAEMON_BIN=$HW_GATE_BIN/daemon python3 scripts/serve_harness.py --model qwen3.6:27b --tag qwen3.6:27b --mode battery --prompts-file benchmarks/prompts/hw-gate/serve-battery.json --max-tokens 256 --thinking off --thinking-effort none --max-think-tokens 0 --dflash on --port 18400 --home fable-home/pr-qwen36-tag-dflash-on --out $HW_GATE_EVIDENCE/pr-qwen36-tag-dflash-on.json --serve-log $HW_GATE_EVIDENCE/pr-qwen36-tag-dflash-on.serve.log (exact recipe: $HW_GATE_EVIDENCE/route.sh) | serve.log: DFlash draft loaded: /home/kaden/.hipfire/models/qwen36-27b-dflash-mq4.hfq (layers=5, hidden=5120, block=16) with no HIPFIRE_DFLASH_DRAFT and no --draft. 5/5 turns coherent, recall 5/5, attractor 0: code tau=7.31, prose tau=1.27, factual 'The capital of France is Paris, and the river that runs through it is the Seine.' tau=3.0, JSON tau=7.0, '17 + 26 = 43 / Answer: 43' tau=4.67. Harness dflash=on path-proofs passed, exit 0. | pr-qwen36-tag-dflash-on.{out,json,serve.log} |

| Does a PATH-form request for the symlinked install (qwen3.8-27b.mq4-xt -> ~/qcal/...) get registry identity via canonical comparison, resolve the (also symlinked) shared MQ4 sidecar, and keep /health.model as the requested path (Sol gap 2; serve/mod.rs:1216)? | same harness recipe on GPU 1, PR binary, --model /home/kaden/.hipfire/models/qwen3.8-27b.mq4-xt --tag qwen3.8:27b-mq4-xt --dflash on --port 18401 | serve.log: DFlash draft loaded: /home/kaden/.hipfire/models/qwen38-27b-dflash-mq4.hfq (layers=5, hidden=5120, block=8), [hipfire] pre-warmed /home/kaden/.hipfire/models/qwen3.8-27b.mq4-xt (health reported the path, so the harness warm check passed). Tag policy applied through the path (kv q8/vmm, max_seq 262144). 5/5 coherent, recall 5/5, tau 10.0 / 2.0 / 4.67 / 9.33 / 7.5; exit 0. | pr-qwen38xt-path-dflash-on.{out,json,serve.log} |

| A/B against master: same tag, same config (dflash=auto) — does master run AR while the PR picks up the sidecar, with identical coherence? | HIPFIRE_KERNEL_CACHE=fable-home/kcache-base route.sh base-qwen36-tag-dflash-auto-gpu3b 3 $HW_GATE_BASE_BIN qwen3.6:27b qwen3.6:27b auto ; then HIPFIRE_KERNEL_CACHE=fable-home/kcache-pr route.sh pr-qwen36-tag-dflash-auto-gpu3b 3 $HW_GATE_BIN qwen3.6:27b qwen3.6:27b auto | master: no draft line, tau=None on all 5 turns, decode 32.2–36.3 tok/s (AR), 5/5 coherent. PR: DFlash draft loaded: …/qwen36-27b-dflash-mq4.hfq, tau 7.87 / 1.53 / 3.0 / 9.67 / 5.0, decode 42–120 tok/s, 5/5 coherent with the same answers (Paris/Seine, Alice/34/Lisbon, Answer: 43). Both exit 0. This is the whole behavior delta of the PR under auto, and nothing else moved. | pr-qwen36-tag-dflash-auto-gpu3b.* |

| Does dflash_mode=on fail closed BEFORE any load when the declared sidecar is not on disk (main.rs:2724)? | HIPFIRE_NO_REGISTRY_FETCH=1 HIPFIRE_HOME=fable-home/nodraft-home HIPFIRE_MODELS_DIR=fable-home/nodraft-models (contains only a HARDLINK of qwen3.6-27b.mq4, inode 790866, no draft) HIP_VISIBLE_DEVICES=3 $HW_GATE_BIN/hipfire run qwen3.6:27b --spec dflash -n 8 'Say hi' | exit=1 in 1.6 s, no daemon load: hipfire: DFlash draft qwen36-27b-dflash-mq4.hfq is not pulled; run \hipfire pull qwen3.6:27b` or set developer.dflash_draft`. | on-missing-sidecar-fails-closed.run.log |

| Does dflash_mode=auto with the sidecar absent warn once and run AR with a coherent answer (main.rs:2729)? | same nodraft-models dir, config [speculation] mode=auto dflash=auto ($HW_GATE_EVIDENCE/auto-missing-sidecar.config.toml), HIPFIRE_KERNEL_CACHE=fable-home/kcache-d1, HIP_VISIBLE_DEVICES=1: $HW_GATE_BIN/hipfire run qwen3.6:27b -n 64 --no-stream 'What is the capital of France, and which river runs through it? Answer in one sentence.' | [hipfire] DFlash draft qwen36-27b-dflash-mq4.hfq not pulled; running AR — \hipfire pull qwen3.6:27b`(printed twice — load_params and the post-selector re-resolve in run_command both emit it; cosmetic), thenThe capital of France is Paris, and the river that runs through it is the Seine.` exit=0. | auto-missing-sidecar-runs-ar.run.log |

| Does dflash_mode=on with an UNLOADABLE explicit draft refuse the load in the daemon (loader lib.rs:1978), drain the target's VRAM (lib.rs:1700), not compound on the lazy retry, and leave the daemon able to load another model (Sol gap 3)? | PR binary, GPU 0, HOME=HIPFIRE_HOME=fable-home/d2-on-baddraft with [speculation] mode=dflash dflash=on and [developer] dflash_draft=/home/kaden/.hipfire/models/lfm2.5-1.2b.mq4 ($HW_GATE_EVIDENCE/on-unloadable-draft.config.toml): hipfire serve 127.0.0.1 18500; then curl POST /v1/chat/completions model=qwen3.6:27b; rocm-smi; curl model=lfm2.5:1.2b; rocm-smi | Prewarm loaded all 64 layers, then pre-warm failed: … load failed: DFlash draft required (dflash_mode=on) but failed to load (…/lfm2.5-1.2b.mq4): draft: failed to parse DflashConfig from HFQ metadata. GPU: gfx1201 (32404 MB free / 32624 MB total); serving lazily — the 15 GB target was released. Request 1 retried the full load and was refused identically (again 32404 MB free). Request 2 (lfm2.5:1.2b) loaded on the same daemon and answered The capital of France is Paris. at 269 tok/s; steady VRAM on GPU 0 = 1.5 GB (lfm2.5 + KV), not 15 GB + lfm2.5. rocm-smi returned to 60 MB after stop. | on-unloadable-draft-serve-requests.log |

| Do the rm semantics hold on the real binary: foreign same-basename path removes only itself (round-1 regression), shared sidecar kept while a sibling declarer is on disk and removed with the last (main.rs:1774–1874)? | DUMMY files only, no model run: HIPFIRE_MODELS_DIR=fable-home/rm-scratch/models (qwen3.6-27b.mq4, qwen36-27b-dflash-mq4.hfq, qwen3.8-27b.mq4, qwen3.8-27b.mq4-pro, qwen38-27b-dflash-mq4.hfq) + elsewhere/qwen3.6-27b.mq4; hipfire rm --yes <elsewhere path>, rm --yes qwen3.8:27b-mq4-pro, rm --yes qwen3.8:27b, rm --yes qwen3.6:27b | foreign path: only that file removed, installed target + sidecar untouched. mq4-pro: target removed, keeping DFlash sidecar qwen38-27b-dflash-mq4.hfq: still declared by qwen3.8:27b. qwen3.8:27b: target + shared sidecar removed (xt's file absent, so not a keeper). qwen3.6:27b: target + its own sidecar removed. | rm-semantics-dummy-files.log |

| Failed experiments (recorded, not evidence against the PR): why did the first dflash=auto A/B and the first auto+missing attempt fail on BOTH binaries? | base-qwen36-tag-dflash-auto, pr-qwen36-tag-dflash-auto, -gpu3 (first attempt), first auto-missing run; probe pr-lfm25-gpu2-probe | hipModuleGetFunction … "embedding_q8_batched" … named symbol not found on master and PR alike. Cause: I launched three daemons from two builds concurrently out of one checkout; they share the CWD-keyed JIT cache pr/.hipfire_kernels/gfx1201 and the racing writes left embedding_q8_batched.hsaco (6432 B) with no kernel symbol plus a hash file certifying it (verified with strings; sibling blobs fine). I removed that one cache entry from the checkout and re-ran every affected route with a private HIPFIRE_KERNEL_CACHE; all passed. Pre-existing cache design, my setup error, not a PR behavior. Note the PR's dflash=on routes were unaffected because they got good blobs first. | pr-lfm25-gpu2-probe. |

unproven:

  • hipfire pull <target> fetching the new dflash sidecar (download, sha256/size verification of the 12 new v1.json sidecar digests, the pull <tag>-draft overlap) — no network in the sandbox; needs a networked host and a pull of qwen3.6:27b into an empty models dir.

  • gfx1100 (hipx lane) for the dflash=on/auto routes — this host is 4x gfx1201 only; the gate's own hipx battery ran with dflash off. The 7900 XTX numbers in the PR body are the author's.

  • The llama-carrier (arch 0/1) draft fallback that the author says stays log-and-AR under on — no such fixture with a declared dflash sidecar exists here (none declared in the registry either).

  • scripts/registry_gen.py's dflash annotation path — executes HF metadata lookups; not run.

  • Whether the registry policy change is what the maintainer wants: 20 dense targets now pull an extra 0.55–1.21 GB by default; cross-quant pairings (qwen3.5:9b-mq6 and 9b-mq3 -> the mq4 draft; qwen3.8 mq3 tiers -> mq3 draft) while qwen3.8 mq5/mq6 tiers get no sidecar although mq5/mq6 drafts are registered — human judgment, not hardware.

rationale: The hard floor fired on registry/models.json and registry/v1.json, so this is a hold regardless of what I found; what I found is that the code side is proven on hardware and clean. Every gap Sol listed is now closed with a decoded turn: (1) crates/hipfire-cli/src/main.rs:2668/2690 resolve_dflash_sidecar — a bare tag under on and auto loads qwen36-27b-dflash-mq4.hfq with tau 1.3–9.7 and 5/5 coherent turns (pr-qwen36-tag-dflash-on, pr-qwen36-tag-dflash-auto-gpu3b), while the identical master run is tau=None AR (base-qwen36-tag-dflash-auto-gpu3b) — that A/B is the entire behavior delta and the answers match; (2) main.rs:2453 registry_entry_for_path — the symlinked qwen3.8-27b.mq4-xt requested by PATH resolved its entry, its symlinked sidecar, tag policy, and health reported the path (pr-qwen38xt-path-dflash-on, serve/mod.rs:1216); (3) crates/hipfire-loader/src/lib.rs:1978 + :1700 — an unloadable draft under on is refused, the daemon itself reports 32404/32624 MB free after rollback, the lazy retry does not compound, and lfm2.5:1.2b loads and answers on the same daemon (on-unloadable-draft.serve.log, on-unloadable-draft-serve-requests.log); (4) main.rs:2724/2729 — on with the sidecar absent exits 1 before load with the pull hint; auto warns and gives a correct AR answer (on-missing-sidecar-fails-closed.run.log, auto-missing-sidecar-runs-ar.run.log); (5) main.rs:1774–1874 rm — the round-1 basename deletion is gone and the shared-sidecar keep/remove rule behaves, on dummy files. No regression against master was demonstrated; the only new wart is the duplicated warning line from run under auto+missing (load_params and the post-selector re-resolve both print). To lift the hold a human needs to (a) accept the registry policy in registry/models.json:102ff / registry/v1.json (20 targets gain a sidecar pull; note the cross-quant pairings and the missing mq5/mq6 pairings), and (b) decide the staging question: crates/hipfire-registry/src/lib.rs:23 points DEFAULT_REGISTRY_URL at master/registry/v1.json and load() prefers network/cache over bundled, so on beta a networked user's CLI will not see dflash at all until this registry reaches master — the feature is inert on staging unless HIPFIRE_NO_REGISTRY_FETCH=1 or the fetch fails. Two sandbox notes for the record, both in $HW_GATE_EVIDENCE/README.md: my first run attempt mis-pointed HIPFIRE_MODELS_DIR at an empty dir and the CLI's existing auto-pull touched the network (~2 GB, killed at 60 s, partial deleted, ~/.hipfire untouched); and I removed one corrupted entry my own concurrent runs wrote into pr/.hipfire_kernels/gfx1201 (embedding_q8_batched.*), which is why several first-attempt .out files in the evidence dir show a prefill failure on master and PR alike.

hard floor: ['policy_paths: registry/models.json,registry/v1.json']

soft floor: ["coverage_gaps: ['The batteries do not show DFlash loader logs, tau, or other evidence that a registry-declared draft was loaded; qwen3.6:27b and qwen3.8:27b-mq4-xt decode rates are consistent with AR.', 'No tag-versus-installed-path or symlink parity route exercised registry_entry_for_path.', 'No dflash_mode=on missing or unloadable-draft failure followed by a valid load exercised rollback_unfinished_qwen35 and GPU-pool recovery.', 'Target pull, shared-sidecar rm behavior, explicit-draft precedence, final off stripping, and registry sidecar digest/download behavior were not exercised by hardware evidence.', 'registry/models.json and registry/v1.json are policy files and require human review.']", 'model needs-human']

@hipfire-fable hipfire-fable Bot added the needs-human hw-gate reviewer requests a human decision label Sep 4, 2026

@hipfire-fable hipfire-fable Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The core claim holds on hardware: with no env var and no --model-draft, auto on qwen3.6:27b and on on qwen3.8:27b-mq4-xt both load the registry sidecar and every request runs drafter=dflash with coherent output, while master on the same route stays AR; on+missing, auto+missing, on+unloadable, auto+unloadable and explicit-draft precedence all behave as described, and the pull path fetched a sidecar with a matching sha256. Two things need fixing before this can land: the new fail-closed arm in finish_qwen35_load leaves about 5 GB of VRAM behind after a refused on-load (23.98 GB vs 18.81 GB with the same next model resident), and since serve keeps retrying lazily that compounds; and hipfire rm <variant> now deletes a DFlash draft that sibling targets and the standalone draft tag still own. The registry policy files also need the maintainer's eyes (deny_unknown_fields means pre-PR binaries will reject the new remote v1.json until they upgrade). Evidence is in the fable-evidence directory; decision is hold. Hard floor fired on registry/models.json + registry/v1.json, so the decision is hold regardless; what I did was close Sol's coverage gaps so the human review is about the policy files and two concrete defects, not about whether the feature works. The feature works: with the PR binary (hipfire 0183affc…, daemon b1b14059…) and no env var or --model-draft, dflash_mode=auto on qwen3.6:27b and dflash_mode=on on qwen3.8:27b-mq4-xt both loaded the registry-declared sidecar ('DFlash draft loaded: …qwen36-27b-dflash-mq4.hfq' / '…qwen38-27b-dflash-mq4.hfq'), every request logged drafter=dflash with tau 1.85–10.0, and all 20 decoded turns plus the hipfire run qwen3.6:27b --spec dflash output are correct and non-degenerate; the base binary on the identical route is AR (tau=None) — pos-auto-qwen3.6-27b-{pr,base}, pos-on-qwen3.8-27b-mq4-xt-pr, run-tag-spec-dflash-qwen3.6-27b-{pr,base}. The four claimed edge semantics are evidenced on hardware: on+missing bails with the pull hint (neg-on-missing…), auto+missing warns and runs AR (neg-auto-missing…), on+unloadable fails closed in the daemon (neg-on-unloadable…), auto+unloadable logs and falls back (neg-auto-unloadable…), explicit draft beats the sidecar (explicit-draft-wins…). The pull→sidecar→on-load chain was also exercised, unintentionally: my probe serve had no default_model, fell back to the schema default tag qwen3.5:9b, and pull_command fetched the target and its DFlash sidecar from Hugging Face with sha256s matching v1.json, then loaded the draft — that is a sandbox breach on my side (the host has network), disclosed in rollback-reuse-same-daemon-pr/README.txt; the 5.9 GB was deleted afterwards. Two things the author should fix before this lands: (1) the new fail-closed arm at crates/hipfire-loader/src/lib.rs:1968 leaves ~5.17 GB resident (vram-leak-summary.txt: 23.98 GB vs 18.81 GB with the same next model loaded), and because serve keeps 'serving lazily' each request retries the failing load, so a corrupt sidecar under on becomes a VRAM drain rather than a clean refusal — check whether rollback_unfinished_qwen35 frees token_embd/lm_head, and whether the mtp=on path shares the bug; (2) rm at main.rs:1783 deletes a draft that other pulled targets and the standalone draft tag still own (rm-shared-dflash-sidecar-probe.txt) — skip sidecars referenced by another present target or that are themselves a registry entry. Also worth the maintainer's attention: registry ModelEntry is deny_unknown_fields (lib.rs:177), so binaries older than this PR will fail to parse the remote v1.json once it carries dflash and drop to stale-cache/bundled with a warning — the same thing happened when sampling_profiles was added on 2026-07-22, so it is precedented, but it is the real cost of the policy change. A gate-side note, not a PR defect: base/scripts/hw-gate/run.py exports HIPFIRE_HOME into the harness env, and the CLI reads $HIPFIRE_HOME/config.toml in preference to the harness's /.hipfire/config.toml, so the harness's [speculation]/[memory] sections are silently ignored by mandatory routes (my attempt 1 in attempt1-hipfire-home-leak/ shows '--dflash auto' measuring dflash_mode=off); it is harmless while gate routes are dflash-off, but any future dflash route in run.py will measure defaults unless HIPFIRE_HOME is unset for the harness call.

Kaden-Schutt added a commit that referenced this pull request Sep 4, 2026
…ll declares

hw-gate Fable seat on #686 (hardware probe): `hipfire rm qwen3.8:27b-mq4-pro` deleted qwen38-27b-dflash-mq4.hfq while qwen3.8:27b and qwen3.8:27b-mq4-xt — both declaring that sidecar — were still on disk; those siblings then ran AR under dflash_mode=auto or refused under `on`. Same shape for the mq3 draft (3 targets) and the 9B mq4 draft (3 targets).

rm now removes a declared sidecar only when no OTHER registry entry declaring the same file still has its own target present in the models dir; otherwise it prints `keeping DFlash sidecar <file>: still declared by <tags>`. rm_command is a thin wrapper over rm_with_registry(&RegistryV1) so the rule is unit-testable without env or network.

Tests: rm_keeps_shared_dflash_sidecar_while_sibling_target_present, rm_removes_dflash_sidecar_with_last_declaring_target, rm_without_dflash_declaration_leaves_draft_file_alone.
@github-actions github-actions Bot removed the needs-human hw-gate reviewer requests a human decision label Sep 4, 2026
@Kaden-Schutt

Copy link
Copy Markdown
Collaborator Author

Both regressions the Fable seat found on hardware (run 33852962119) are fixed on this branch:

  • def19e379rm keeps a DFlash sidecar that another installed target still declares (rm_with_registry keeper guard; prints keeping DFlash sidecar <file>: still declared by <tags>; 3 unit tests).
  • e0c3bf095VRAM retained after a refused dflash_mode=on load. Cause: free_qwen35_bundle returns every buffer to the Gpu pool (free_tensor has no size cap, dispatch.rs:3261) and only unload_model drains it (lib.rs:3871-3872); a load that fails in finish_qwen35_load never reaches unload_model. rollback_unfinished_qwen35 now does invalidate_graph_state + drain_pool like unload_model, covering all three callers (CASK eviction failure, dflash_mode=on, and the pre-existing mtp=on failure path — same leak, older than this PR).

Measured on hipx (gfx1100, 24 GB), idle daemon after one refused on load of qwen3.6-27b.mq4 with a 4 MiB /dev/urandom draft under the registry-declared sidecar name, rocm-smi VRAM used:

daemon retained after the refused load
before e0c3bf095 (8ebabd577ed9) 18,950 MB — the whole target stays pooled; daemon reports "6560 MB free"
after (6dd7051f79e4) 281 MB (daemon baseline); "24372 MB free"

Fable's ~5 GB was the net after a second load reused same-sized pool buckets; with nothing loaded afterwards the full 19 GB is visible.

Not addressed here (from the same verdict): dflash_mode=on on a path whose basename matches no registry entry still runs AR silently (main.rs:2626-2628), while the docs say on fails when the draft is missing — true only for registered targets.

@hipfire-sol hipfire-sol Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

hw-gate sol verdict block: Block is mandatory because qwen3.8:27b-mq4-xt failed its selected battery. Independently, crates/hipfire-cli/src/main.rs:2716 breaks the documented HIPFIRE_DFLASH_DRAFT='' opt-out by permitting subsequent sidecar resolution. The central claim—automatic registry-sidecar attachment producing DFlash generation—was not exercised by the captured routes.

@hipfire-fable hipfire-fable Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The gate failure on qwen3.8:27b-mq4-xt is the host, not your PR: every spawn died at hipInit code 100 during the 09:08Z amdgpu/kfd outage and /dev/kfd is still refusing opens, so that battery needs a re-run once the host reboots. Sol's HIPFIRE_DFLASH_DRAFT='' regression does not hold — the daemon's own env read (hipfire-daemon/src/main.rs:1199-1209) still drops the draft on an empty value, so the opt-out is intact. What does block: the new basename-to-entry resolution in resolve_tag makes hipfire rm /elsewhere/qwen3.6-27b.mq4 (a different artifact that only shares the name) delete models/qwen3.6-27b.mq4.triattn.blended_v3.bin and models/qwen36-27b-dflash-mq4.hfq while the registered qwen3.6-27b.mq4 stays installed — master removes only the named file, and the y/N prompt shows just a count. Please only inherit an entry when the path canonicalizes to the models-dir artifact (or never sweep models-dir sidecars for an outside path) and print the delete set; the registry pairings also need a human read by floor rule, and the PR body's '20 targets' is stale (13 after 99c9c7f). Everything else checks out: your rm keeper fix works on the gate binary, all 18 new tests pass, every sidecar digest on this host matches v1.json, and the pre-outage hardware runs on this head's daemon show auto attaching the sidecar with drafter=dflash and on failing closed correctly. The floor already forces block/hold (registry/models.json + registry/v1.json changed; qwen3.8:27b-mq4-xt battery exit 1). I traced the battery failure to the host, not the PR: every spawn died at hipInit code 100 during the 09:08Z amdgpu/kfd outage, kfd is still EINVAL now, and the coherent rows hw-gate.json attributes to that fixture are from a stale 08:25Z JSON (pr686-r3-mandatory-fixture-failure-is-host-outage.txt). Sol's high regression at main.rs:2716 does not hold — the daemon's own HIPFIRE_DFLASH_DRAFT read (hipfire-daemon/src/main.rs:1199-1209, unchanged from master) drops the draft on '' regardless of params.draft, so the opt-out is intact end to end. Block stands on a regression I demonstrated on the gate binaries instead: the new basename-to-entry resolution (crates/hipfire-registry/src/lib.rs:430-438) makes hipfire rm <path> delete models-dir sidecars belonging to a still-installed registered model when the path merely shares the basename (pr686-r3-rm-basename-collision-collateral.txt; base removes only the named file), and the confirm prompt shows only a count. The feature itself is well evidenced: the prior round's pre-outage hardware runs used this head's exact daemon (b1b14059…) and a CLI that differs only by the rm-only commit def19e3 — auto attached the qwen3.6:27b sidecar (drafter=dflash, tau 6.82/1.85/3.25/9.67/5.00, five coherent turns; base ran AR), on-missing failed closed with the pull hint, auto-missing warned and ran AR, explicit draft won, unloadable sidecar under on rolled back — and this round def19e3's keeper logic works on the real binary (pr686-r3-rm-shared-dflash-sidecar-probe.txt), all 18 new/affected unit tests pass on this head (pr686-r3-cargo-test-*.log), every v1.json sidecar digest present on this host matches byte-for-byte, and the daemon ratchet is clean. What changes the decision: (1) restrict basename resolution so a path only inherits an entry when it canonicalizes to <models_dir>/<entry.file> (or have rm never add models-dir sidecars for a path outside the models dir) and list the delete set in the prompt; (2) a maintainer reads the 13 registry pairings; (3) the mandatory batteries re-run on a healthy host — nothing in this diff explains the exit 1.

@hipfire-fable hipfire-fable Bot added the hw-gate-blocked hw-gate reviewer blocked on evidence label Sep 4, 2026
@github-actions github-actions Bot removed the hw-gate-blocked hw-gate reviewer blocked on evidence label Sep 4, 2026

@hipfire-sol hipfire-sol Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

hw-gate sol verdict block: The gate policy requires block on any fixture failure. qwen3.8:27b-mq4-xt failed on gfx1201 while passing on gfx1100, so the cross-architecture load/serve route is not clean. Moreover, the batteries do not prove the central sidecar claim because they do not demonstrate a loaded DFlash draft or exercise the new pull, missing-sidecar, override, rm, and rollback paths.

@hipfire-fable hipfire-fable Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Blocking, but not for the reason the floor fired: the hiptrx qwen3.8:27b-mq4-xt failure is the harness attractor detector flagging a correct one-word compact-JSON answer under temperature-1.0 sampling with DFlash off and MTP drafting — PR and base give byte-identical turns with a pinned seed, so that fixture is clean. The block is the hipfire rm collateral reported last round and still present on e0c3bf0: because resolve_tag now matches any path by basename, hipfire rm /elsewhere/qwen3.6-27b.mq4 deletes the installed model's triattn and dflash sidecars from the models dir (master removes only the named file); please only resolve a path to a registry entry when it canonicalizes to <models_dir>/<entry.file> (or apply sidecar removal only when the removed path is the entry's own file), and note in the PR body that path-form loads now inherit tag policy (max_seq 262144/vmm for qwen3.8 where master used 32768). The good news is that everything else is now proven on hardware on this head: the pool drain makes a refused on load cost 0 bytes of retained VRAM against a clean load, auto pairs and drafts with the registry sidecar, on fails closed cleanly at both the CLI and the daemon and the daemon still serves the next valid load. Minor: the 'not pulled; running AR' line prints twice under hipfire run, and the registry JSON changes still need a maintainer read. The floor's evidence failure is not a PR regression: the hiptrx qwen3.8:27b-mq4-xt battery produced five correct turns and was failed by serve_harness.py:2037-2043 scoring a one-word compact-JSON answer as maxfreq=1.0 (r4-mandatory-exit1-is-detector-false-positive.txt); that run had dflash_mode=off and drafter=mtp, and with a pinned seed PR and base produce byte-identical turns and exit 0 both in the route.sh environment (r4-mandatory-ab-summary.txt) and in the gate's HIPFIRE_HOME-set environment where MTP loads exactly as in the gate run (r4-gatehome-ab-summary.txt). Sol's cited high regression at main.rs:1987 therefore does not exist. What does exist, demonstrated on the head binary against base, is the rm collateral: registry lib.rs:427-438 resolves any path by basename, and main.rs:1785-1828 then deletes the installed entry's triattn and dflash sidecars when asked to remove a same-named file outside the models dir (r4-rm-basename-collision-collateral.txt) — master removes only the named file. That is destructive to a working install and was already reported in round 3 (pr686-r3-rm-basename-collision-collateral.txt) without being addressed by the follow-up commit, so this is a block, not a hold. The same basename resolution also makes path-form loads inherit tag policy (max_seq 262144/vmm/sampling for qwen3.8:27b vs master's 32768 default; r4-gatehome-*.serve.log:75) — not shown to break anything, but unmentioned in the PR body and worth a deliberate decision. Everything else the PR claims is now evidenced on hardware on this head: e0c3bf0 removes the 5.17 GB retention after a refused dflash_mode=on load (per-process VRAM after fail+reload equals a clean control load to the byte, r4-leak-summary.txt); auto pairs and drafts with the registry sidecar (r4-pos-auto-qwen3.6-27b-pr: 'DFlash draft loaded', drafter=dflash, five coherent turns); on fails closed at the CLI with a pull hint when the sidecar is missing and at the daemon when it is unloadable, and the daemon then serves a valid reload with dflash tau 4.25; auto warns and runs AR when missing (the warning prints twice under hipfire run; AGENTS.md says one line). Registry policy files still require a human read regardless.

@hipfire-fable hipfire-fable Bot added the hw-gate-blocked hw-gate reviewer blocked on evidence label Sep 4, 2026
Kaden-Schutt and others added 4 commits September 4, 2026 15:23
The default ~24-token prompt reports prefill_tok_s ~= 363 tok/s while a
4.4k-token prompt on the same binary reports 886: the short-prompt number
is launch overhead, not prefill, and the JSON gave no way to tell.

- Add --prompt-file <PATH>: prompt read verbatim (raw bytes, no trim),
  mutually exclusive with positional PROMPT words.
- Standard-bench JSON gains top-level prompt_tokens (u64, as the daemon
  reports it in done.prompt_tokens), prompt_md5 (hex md5 of the exact
  prompt bytes), prompt_chars, and warnings[]; warn when prompt_tokens
  < 256 that prefill_tok_s measures launch overhead.
- Same three values printed on the stderr banner next to model:/arch:.
- No existing field renamed, nulled, or removed; default prompt bytes
  unchanged (audit 2026-09-02 fix slice: bench-prompt-evidence).
The daemon's done event has no prompt_tokens key; the prompt is
prefill_tokens (rows prefilled) plus cached_tokens (prompt-cache prefix).
Measured on a 7900 XTX the JSON reported prompt_tokens: null and never
warned on the 24-token default prompt.
Kaden-Schutt and others added 15 commits September 4, 2026 20:19
…roken 1)

ModelEntry gains #[serde(default)] dflash: Option<Sidecar> alongside
mtp/dspark; every dense qwen3.5:9b/27b, qwen3.6:27b and qwen3.8:27b
quant-variant entry declares the draft file its <tag>-draft alias
resolves to (same-bit draft where one exists, else the mq4 controller).
hipfire pull/rm now fetch/remove the dflash sidecar like MTP/DSpark.
registry_gen annotates dflash with sha256/size_bytes; v1.json was
mirrored by hand (offline) to exactly what the generator would emit.
…flash Broken 1)

load_params wires entry.dflash into params[draft] when dflash_mode is
auto/on and no explicit draft was given (--model-draft,
developer.dflash_draft, params[draft] all win): on without the pulled
file bails with a 'hipfire pull <tag>' hint, auto logs one line and
runs AR. run/bench re-resolve after the final CLI selector so
config-off + --spec dflash still pairs; serve inherits it via
load_params. Final off still strips the draft. The daemon lowers
dflash_mode into SpecLoadCfg.dflash and the Qwen35 draft-load failure
arm returns a load error (with bundle rollback, mirroring MTP) instead
of AR fallback when on.
Replace the filename auto-match story (no such matcher exists) with the
new behavior: pull fetches the draft sidecar, auto uses it when present,
on requires it, developer.dflash_draft/--model-draft override. Point at
the real 'DFlash draft loaded:' loader line instead of the two log lines
that were never emitted.
The dflash SpecLoadCfg arm mirrors the mtp arm below it; one line of comment
says so. 4154 <= 4155.
resolve_tag matched a bare file name against entry.file but not a path
whose final component is that file name, so `serve --model
~/.hipfire/models/qwen3.8-27b.mq5` (what serve_harness passes) got
entry=None and no dflash sidecar: measured on hipx as tau=None / 37 tok/s
under --speculation dflash while the same tag resolved the draft.
…e canonical target

find_model_path canonicalizes, so a target that is a symlink into another
directory (every ladder artifact on the bench boxes) has a parent with no
draft in it and the registry sidecar was never found. load_params now takes
the models dir and the resolver searches it first, then beside the target.
Regression test with a symlinked target. Measured: serve_harness
--speculation dflash on qwen3.8-27b.mq5 ran AR (tau=None, 37 tok/s) before;
the direct-tag probe resolved the draft (tau=14.2).
Resolving a path-form model to its registry entry changed /health.model
from the requested path to the tag. serve_harness's warm probe compares
health.model to the launched path by realpath, so it never saw the serve as
warm and killed/respawned it every 180 s (measured: two spawn attempts,
zero turns). Keep the entry lookup for sidecars and policy; name the served
model the way it was requested.
Measured on a 7900 XTX (serve_harness session_coding, greedy, thinking off,
q8 KV): qwen3.8:27b-mq5 + its mq5 draft completes turns 1-2 (tau 3.6/3.5)
then dies at turn 3, ctx ~4.9k, with spec_step hipMemCreate out of memory;
every later turn is an empty response. The same session under AR passes all
8 turns (13.4k ctx, 38.1 -> 34.4 tok/s). 18.7 GB weights + ~5 GB fixed
residency + 1.7 GB draft leaves no room for KV growth. Drop the sidecar
from the 27B mq5/mq6 tiers (and qwen3.5:27b-mq6); mq4-tier and below keep
theirs (measured 202 tok/s on qwen3.8:27b).
…ll declares

hw-gate Fable seat on #686 (hardware probe): `hipfire rm qwen3.8:27b-mq4-pro` deleted qwen38-27b-dflash-mq4.hfq while qwen3.8:27b and qwen3.8:27b-mq4-xt — both declaring that sidecar — were still on disk; those siblings then ran AR under dflash_mode=auto or refused under `on`. Same shape for the mq3 draft (3 targets) and the 9B mq4 draft (3 targets).

rm now removes a declared sidecar only when no OTHER registry entry declaring the same file still has its own target present in the models dir; otherwise it prints `keeping DFlash sidecar <file>: still declared by <tags>`. rm_command is a thin wrapper over rm_with_registry(&RegistryV1) so the rule is unit-testable without env or network.

Tests: rm_keeps_shared_dflash_sidecar_while_sibling_target_present, rm_removes_dflash_sidecar_with_last_declaring_target, rm_without_dflash_declaration_leaves_draft_file_alone.
hw-gate Fable seat on #686: a daemon that went through one refused dflash_mode=on load held ~5.17 GB more VRAM with the next model resident than a clean daemon, compounding under serve's lazy retry. Cause: free_qwen35_bundle returns every buffer to the Gpu pool (free_tensor has no size cap, dispatch.rs:3261) and only unload_model drains it (lib.rs:3871-3872); a load that fails in finish_qwen35_load never reaches unload_model, so the whole target stayed pooled and the next load reused only the same-sized buckets.

rollback_unfinished_qwen35 now mirrors unload_model: invalidate_graph_state + drain_pool after the frees. Covers all three callers: CASK eviction failure, dflash_mode=on draft failure, and the pre-existing mtp=on head failure (same leak, older than this PR).
… artifact

hw-gate Fable seat on #686 (run 33889233321): resolve_tag matched any path by its final component, so `hipfire rm /elsewhere/qwen3.6-27b.mq4` — a different file sharing the basename — resolved to the qwen3.6:27b entry and deleted the installed model's triattn and DFlash sidecars while the model itself stayed; a same-basename foreign file loaded by path inherited the entry's sidecar and kv/max_seq policy.

registry: resolve_tag drops the file_name() arm (a path is not a tag); bare entry.file names still resolve; new entry_for_file for exact matches.

cli: registry_entry_for_path(paths, registry, input) — a path-form input resolves only when canonicalize(input) == canonicalize(models_dir/entry.file) for some entry, so the -xt symlink into ~/qcal still matches by target and a lookalike elsewhere never does. rm, run, bench, and serve (incl. the pre-warm thread) route through it. dflash_mode=on on a path with no entry and no explicit draft now fails closed: "DFlash draft required (dflash_mode=on) but <path> is not a registry-managed artifact; pass developer.dflash_draft or use the registry tag" (auto still serves it as a bare artifact) — closes Fable's earlier note that `on` ran AR silently there. The daemon only consumes CLI-lowered params and needs no change.

cargo test -p hipfire-registry: 21 passed (2 new); -p hipfire-cli: 220 passed (4 new).
@Kaden-Schutt
Kaden-Schutt force-pushed the fix/dflash-registry-pairing branch from 876cf28 to c86712d Compare September 4, 2026 20:19
@github-actions github-actions Bot removed the needs-human hw-gate reviewer requests a human decision label Sep 4, 2026
# Conflicts:
#	crates/hipfire-cli/map.md
#	crates/hipfire-runtime/map.md
Kaden-Schutt added a commit that referenced this pull request Sep 4, 2026
…, pool drain on refused load, path-identity fail-closed) to staging

Policy floor (registry/models.json, registry/v1.json) means the decide seat can never land this; merged by Main on the gate's hardware evidence after four rounds of Fable findings, all fixed and measured:
- pool drain on refused load (d233d2a): 18,950 MB retained -> 281 MB, and the same daemon then serves qwen3.8+draft at -20 KB vs a clean daemon (hipx gfx1100)
- shared-sidecar rm guard (def19e3): rm no longer deletes a sidecar another target still declares
- path-basename identity (876cf28): registry_entry_for_path requires canonicalize equality, so a foreign /elsewhere/qwen3.6-27b.mq4 gets no sidecar and dflash_mode=on fails closed instead of running AR silently

Enablement is unchanged: dflash_mode default stays off and the sidecar is resolved only under auto/on, so a paired draft on disk still never drafts until the user opts in. The only user-visible delta is pull size: +0.55 GB (9B) / +0.92-0.98 GB (27B). 249 tests pass in hipfire-registry + hipfire-cli at 397a366 (includes the beta merge with regenerated cli/runtime crate maps).
@Kaden-Schutt

Copy link
Copy Markdown
Collaborator Author

Merged to beta as 7c09be92e (branch tip 397a3667c).

The gate could never land this itself: registry/models.json + registry/v1.json are policy paths, so the decide seat's hard floor refuses regardless of lane colour. Merged by Main on the accumulated hardware evidence, with the product question answered explicitly — pairing does not change enablement. dflash_mode default stays off, the sidecar is resolved only under auto/on, so a paired draft on disk still never drafts until the user opts in. The only user-visible delta is pull size: +0.55 GB (9B), +0.92–0.98 GB (27B).

All four rounds of seat findings are in, each measured rather than argued:

  • pool drain on refused load (d233d2af1) — 18,950 MB retained → 281 MB; the same daemon then serves qwen3.8+draft at −20 KB vs a clean daemon
  • shared-sidecar rm guard (def19e379)
  • path-basename identity (876cf28d0) — canonicalize equality, and dflash_mode=on on a non-registry path fails closed instead of silently running AR

249 tests pass in hipfire-registry + hipfire-cli at the merged tip. The crates/hipfire-{cli,runtime}/map.md 409 was the usual generated-block conflict; regenerated with scripts/check-crate-maps.py.

Kaden-Schutt added a commit that referenced this pull request Sep 5, 2026
…iffs

Two policy gaps this ladder exposed.

1. The gate never ran DFlash. #686 (draft sidecars), #691 (draft ctor
   rollback), #692 (primer replay) and #702 (dedicated verify kernels) all went
   through with every lane green while speculation never once executed. #692's
   DFlash-arm defect -- primer replay systematically missing the most recent
   assistant body -- was found only because a seat thought to drive twenty turns
   by hand. That is not a gate.

   The load bucket now runs `battery-dflash` and the serve bucket
   `chain-dflash`: the same prompts with `--dflash on` and an explicit
   `--draft`. `on` rather than `auto` because `auto` silently falls back to AR
   when the draft is missing, and a route that can pass without speculating
   proves nothing. The draft is named explicitly because the canonical xt trunk
   is a symlink out of the models dir, so the daemon's filename auto-match finds
   nothing and would run AR.

   `dflash_draft` is a candidate LIST because the lanes hold different drafts:
   hiptrx has qwen36-27b-dflash-mq4.hfq and no qwen38, hipx has
   qwen38-27b-dflash-mq4.hfq and no qwen36. A lane speculates with the first
   candidate it holds; a lane holding none records `skip`.

   `skip` is neither pass nor fail. The aggregation was
   `all(status == "pass")`, which would have counted a skip as a fixture
   failure -- a false negative on evidence the host never had -- while treating
   it as a pass would claim coverage that did not happen. Skips are recorded and
   reported, and a genuine failure alongside a skip still fails.

   Coverage is asymmetric until both hosts hold both drafts. Pulling
   qwen38-27b-dflash-mq4.hfq to hiptrx and qwen36-27b-dflash-mq4.hfq to hipx
   (0.92 GB each) makes it symmetric; that is a disk decision, so the evidence
   says `skip` rather than silently pulling.

2. Sol refused hardware for any diff touching a filesystem path, which caught
   #689 for adding `--prompt-file` to `hipfire bench` and cost that rung a lane
   until `hw-run` overrode it. hipfire is a CLI inference engine: users name
   models, prompts, drafts and sidecars at invocation, and the gate's own
   harness passes exactly those flags. sol.md now separates whose path it is --
   an explicit argument is ordinary product work; credentials, dotfiles, SSH or
   cloud config, /proc or /sys beyond device enumeration, assembled traversal,
   or a read whose result leaves the process still warrant refusal.

Tests: eight new cases in scripts/hw-gate/tests/test_run.py covering flag
translation (battery-dflash -> `--mode battery --dflash on --draft ...`), plain
battery never receiving a draft, per-lane draft selection, skip-not-fail with
the harness never invoked, chain-dflash keeping its own prompts, the
skip-vs-genuine-failure aggregation, and a manifest assertion that the buckets
actually carry the routes. 113/113 hw-gate tests pass.
ghazni101 pushed a commit to ghazni101/hipfire that referenced this pull request Sep 5, 2026
Second live run on warpfront#686 (33849478193): Sol authorized hardware, both
lanes started, both failed inside 40 s with
"[Errno 2] No such file or directory: 'cargo'". No GPU was ever touched.

Both runners are systemd services. `actions-runner/.path` was written at
install (2026-07-11) and is the distro default PATH; the service
environment has no ~/.cargo/bin, no /opt/rocm/core/bin, no ROCM_PATH.
The warpfront#679 evidence runs were driven by hand from a login shell, which is
why this never surfaced. Nothing on either host changed.

hw-run and fable-decide now import the runner user's login PATH and the
ROCm variables (ROCM_PATH, HIP_PATH, HSA_PATH, HIP_PLATFORM,
LD_LIBRARY_PATH) via GITHUB_PATH / GITHUB_ENV before the build steps.
Verified under a bare service environment on hiptrx: the step yields
ROCM_PATH=/opt/rocm/core, HIP_PATH, LD_LIBRARY_PATH, and cargo/hipcc/omp
resolve. hipx's login shell carries the same variables.
ghazni101 pushed a commit to ghazni101/hipfire that referenced this pull request Sep 5, 2026
…t's raw output on a no-decision

Run 33866758629 (warpfront#702) ended the decide phase in 10 s with "omp decide: no
JSON object in assistant text", and the uploaded fable-evidence/ contained
warpfront#700's fable-summary.md and warpfront#686's route outputs: the workflow does
`mkdir -p fable-evidence fable-home` in a reused runner workspace, so every
session inherits the previous PR's files and can cite them as its own.
`rm -rf` both before the mkdir.

On the no-JSON path review.py discarded the assistant text it had already
extracted, so the artifact carried nothing to diagnose the failure with.
decision.json now records `fable_error` and `fable_raw`
{assistant_text_tail, stderr_tail}; the step log gets the tail too.

103/103 in scripts/hw-gate/tests; workflow YAML parses.
ghazni101 pushed a commit to ghazni101/hipfire that referenced this pull request Sep 5, 2026
Every rung of the 2026-09-04 ladder hit the same 409 on the staging merge:
`crates/*/map.md` carries a `<!-- crate-map:generated -->` block that both
branches regenerate, so any two PRs touching the same crate conflict there
while their real code merges cleanly. warpfront#689, warpfront#690, warpfront#691, warpfront#686, warpfront#687, warpfront#688 and
warpfront#682 all needed the same three manual steps -- merge staging in, regenerate the
block with scripts/check-crate-maps.py, merge -- six of them tonight. A gate
that decides merge-staging and then holds on a generated file is asking a human
to run a script, which is not review.

On a 409 the decide phase now retries locally: merge staging into the PR head,
and if the conflicted set is generated maps only, re-run check-crate-maps.py
for those crates, commit, and merge the result.

The retry is deliberately narrow, because auto-resolving conflicts is exactly
where a gate can do damage:
- if ANY conflicted path is not a `map.md`, it declines and the hold stands
  with the offending paths named -- a real code conflict must reach a human
- it regenerates rather than picking a side, so the committed block is what the
  tree actually generates, not whichever branch won
- a failed regeneration, a git error, or a timeout all decline rather than
  force

Test: `test_generated_map_retry_refuses_real_code_conflicts` builds a real repo
with a conflicting `.rs` and asserts the retry returns no merge SHA and names
the file. The guard is the part worth pinning; the happy path is exercised by
the ladder itself.

122/122 hw-gate tests pass.
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