Pull requests / #2591

#2591 spec: keep probabilistic admission independent of candidate draws

open · draft · @KeyanHu-git · 0 コメント · GitHub で見る

Server & APIAMD / HIPNVIDIA / CUDAModels & quantsWindows

本文

## Title

spec: keep probabilistic admission independent of candidate draws

Refs #2028.

## Summary

With `STRATA_SPEC_PROB=1`, gating a draft by its own sampled probability changes which draws from q reach rejection sampling. The verifier still uses q, so the resulting distribution is biased. Selecting suffix lookup only when its first token matches the sampled MTP token introduces the same problem.

This change makes admission and source selection independent of the candidate being judged. It uses the existing `top` gate and fixes the source planner's learned estimates for each request. Thanks to @KarlGrier for the report and derivation in #2028.

## What changed

- Default the opt-in probabilistic mode to `top`, which gates on a property of q before the candidate draw. Retain explicit `STRATA_SPEC_PROB_GATE=pick` and label it approximate in the startup message and documentation.
- Choose between probabilistic MTP and suffix lookup using committed context, the configured MTP window and a request-initial copy of `DraftPolicy`. Remove the sampled-token match and draw-dependent window length from that choice in both serve and CLI.
- Keep learning in the live policy for subsequent requests. Its observations cannot affect source selection within the current request: discarded lookahead can reuse position-keyed random draws, so even an earlier round's draft timing can reveal information about a candidate used later.
- Add exact CPU enumeration through the existing verifier and planner, covering the admission rule and draw-conditioned source selection. Document the proposal-law contract and the numerical conditions needed for real-model comparisons.

The normal exact-match and coupled modes retain their existing selection rules. This patch adds no new gate mode or window-length algorithm.

## Extra Notes

### Exact regression checks

`spec_admission_test` enumerates 900 two-token distribution/threshold combinations without sampling noise. It also checks the default and explicit `pick` configurations, and exercises the actual planner with draw-dependent timing observations.

| Case | Target P(A) | Result P(A) |
| --- | ---: | ---: |
| Own-draw gate, q(A)=0.875, threshold=0.5 | 0.5000 | 0.5625 |
| Distribution-based gate, same inputs | 0.5000 | 0.5000 |
| Source choice conditioned on draft timing | 0.5000 | 0.4375 |
| Request-fixed source planner | 0.5000 | 0.5000 |

Both configurations passed on macOS and Windows. CTest registers them separately. The existing verifier suite was also rerun on macOS: 72 cases, zero failures.

```sh
c++ -std=c++20 -O2 -Wall -Wextra -Werror -Iinclude \
  src/core/spec_admission_test.cpp src/spec/draft_policy.cpp \
  -o spec_admission_test
env -u STRATA_SPEC_PROB_GATE ./spec_admission_test
STRATA_SPEC_PROB_GATE=pick ./spec_admission_test pick
```

### Measured speed on RX 7900 XTX

Candidate `e46bc229`, upstream `296b59d2`; Windows, Ryzen 5 7600X, RX 7900 XTX, IQ1_M. Both use context 131072, 5759 GPU expert slots and a 12287 MiB RAM expert arena. The model is partially resident: file reads are included. Adaptive swaps and PCIe fraction are zero. Both builds use the same HIP Release configuration; the upstream binary contains the earlier CLI-only logits probe, which this serve-protocol test does not enter.

Five interleaved paired blocks, reversing arm order on alternate blocks. Each fresh engine gets a 16-token warmup, followed by English and repeated-context requests at temperatures 0.6 and 1.0, 64 output tokens each: 80 measured requests. Prompts, seeds and request order match within pairs. Default IQ arithmetic; timing instrumentation disabled. The speed runs report `spec=6, mtp_max=4, lookup=3` (lookup expands the configured spec=4 graph). Upstream probabilistic default uses `pick`; the candidate uses `top`.

Positive speed change means faster. The percentage is the median of five paired ratios `upstream decode_ms / candidate decode_ms - 1`; it is not the ratio of the two marginal medians.

| Mode | Input | Temp | Upstream decode ms | Candidate decode ms | Paired speed change | Faster pairs | Identical token streams |
| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: |
| Ordinary | English | 0.6 | 1447.4 | 1384.8 | +1.94% | 4/5 | 4/5 |
| Ordinary | English | 1.0 | 1206.2 | 1218.9 | +2.12% | 4/5 | 5/5 |
| Ordinary | Repeated context | 0.6 | 1719.0 | 1616.3 | +5.51% | 5/5 | 5/5 |
| Ordinary | Repeated context | 1.0 | 1799.1 | 1706.6 | +8.00% | 3/5 | 5/5 |
| Probabilistic | English | 0.6 | 1350.5 | 1486.6 | -9.20% | 0/5 | 0/5 |
| Probabilistic | English | 1.0 | 1252.9 | 1293.2 | -9.20% | 1/5 | 0/5 |
| Probabilistic | Repeated context | 0.6 | 1732.5 | 1540.5 | +6.90% | 5/5 | 5/5 |
| Probabilistic | Repeated context | 1.0 | 1693.0 | 1697.1 | +0.74% | 3/5 | 4/5 |

The probabilistic English workload is about 9.2% slower by the paired median at both temperatures. Repeated context improves by 6.9% at 0.6 and 0.7% at 1.0. These results measure the combined admission/planner change, including changed output paths; they do not isolate a kernel cost. Ordinary-mode pairs match token-for-token in 19/20 cases. The remaining mismatch under the default arithmetic/timing-adaptive path has not been isolated, so the positive ordinary-mode timings are not presented as a proven optimization. All 40 paired comparisons match prompt-reuse counts.

<details>
<summary>Median wall time, first-token latency and decode file reads</summary>

| Mode | Input | Temp | Wall ms, upstream / candidate | TTFT ms, upstream / candidate | Decode file blobs, upstream / candidate |
| --- | --- | ---: | ---: | ---: | ---: |
| normal | english | 0.6 | 1546.0 / 1469.0 | 125.0 / 109.0 | 87 / 87 |
| normal | english | 1.0 | 1985.0 / 1922.0 | 781.0 / 750.0 | 84 / 84 |
| normal | source | 0.6 | 5344.0 / 5344.0 | 3656.0 / 3718.0 | 654 / 641 |
| normal | source | 1.0 | 5016.0 / 4953.0 | 3266.0 / 3297.0 | 691 / 651 |
| prob | english | 0.6 | 1453.0 / 1579.0 | 109.0 / 110.0 | 89 / 105 |
| prob | english | 1.0 | 1969.0 / 1984.0 | 750.0 / 735.0 | 85 / 89 |
| prob | source | 0.6 | 5375.0 / 5172.0 | 3687.0 / 3703.0 | 641 / 582 |
| prob | source | 1.0 | 4954.0 / 4922.0 | 3297.0 / 3266.0 | 655 / 642 |

</details>

### Fixed-position sampling pilot

A separate run uses the final binary in both arms with `STRATA_IQ_MT_MIN=1`, suffix lookup off and graph size 4. The speculation-off reference sets `spec-min-p=2` and verifies zero drafts; the candidate uses default `top` at threshold 0.5. Sampling is `top_k=20, top_p=0.95, min_p=0.05`. The issue's 108-token prompt is restored from a pinned checkpoint at token 106. Each request generates four tokens. As in #2028, count `=` (token 28) after `[17,13,7860]` (`2. Sum`).

Two reversed-order blocks use independent seeds and a planned 400 requests per arm/temperature. The predeclared 900-second overall deadline stopped the final cell seven requests short: 1593 completed requests, with actual counts below. Samples were not extended after inspecting outcomes.

| Temp | Requests off / top | Off: equals / eligible | Top: equals / eligible | Difference, pp | 95% difference interval, pp | Fisher p, two-sided |
| ---: | ---: | ---: | ---: | ---: | ---: | ---: |
| 0.6 | 400 / 400 | 172/379 (45.38%) | 185/376 (49.20%) | +3.82 | [-3.29, +10.87] | 0.3079 |
| 1.0 | 393 / 400 | 161/327 (49.24%) | 163/333 (48.95%) | -0.29 | [-7.87, +7.30] | 1.0000 |

Intervals use the Newcombe difference of Wilson intervals. Both include zero, but remain wide enough to include several-percentage-point bias. This pilot does **not** establish distribution equivalence or resolve the earlier residual. It tests sampling behavior, not task-answer accuracy.

The earlier stable-arithmetic runtime check also remains valid: six 32-token requests, with token-identical default-vs-top English, default-vs-top suffix-source, and ordinary upstream-vs-candidate pairs. Full CUDA and HIP builds passed; SYCL has not been built.

### Cached-prefix numerical diagnosis

A separate measurement-only build of this patch recorded full logits, q rows, emitted-row status, complete input prefixes and window partitions at the issue's cached `pin=106` path. Input cell 110 predicts the fourth output token (position 111). Five configurations ran nine requests each, including fresh-cache and restored-cache requests and repeated seeds. Prefixes were matched before comparing logits.

| Comparison | Matching prefix pairs | Bit-identical full logits | Maximum absolute logit difference | P(=) difference at temperature 0.6 |
| --- | ---: | ---: | ---: | ---: |
| Stable IQ: threshold-2 off reference vs `top` | 7/9 | 7/7 | 0 | 0 pp |
| Stable IQ: off reference vs true no-MTP | 9/9 | 9/9 | 0 | 0 pp |
| Default IQ: off reference vs `top` | 7/9 | 0/7 | 0.291183 | +4.143849 pp |

Each logits row contains 248320 float32 values. All 20 within-configuration repeat pairs were bit-identical. The two unmatched prefixes in the top comparisons are not pooled into the comparison. A CPU replay using the existing parity test's sampler chain and Philox implementation, then `spec_verify_row`/`spec_sample` with the captured inputs, reproduced all 45 captured target outputs.

This directly demonstrates a numerical channel in the cached-prefix reproduction: the IQ CPU dispatch described in #152 uses different arithmetic for singleton and grouped expert work. `STRATA_IQ_MT_MIN=1` removes that difference on these traces. This does not prove invariance for all contexts or quantify how much of the earlier candidate's full frequency difference came from that channel.

### Cost of consistent IQ arithmetic

Using the same published binary and the same five-pair workload protocol above, a second comparison changed only `STRATA_IQ_MT_MIN`: unset versus `1`. No diagnostic logging was enabled. Positive decode change means faster; positive prefill change means more time. Values are medians of paired ratios.

| Input | Temperature | Eligible pairs | Decode throughput change with stable IQ | Prefill time change |
| --- | ---: | ---: | ---: | ---: |
| English | 0.6 | 5/5 | -1.96% | +1.88% |
| English | 1.0 | 5/5 | -5.89% | +3.67% |
| Repeated context | 0.6 | 5/5 | -10.39% | +0.23% |
| Repeated context | 1.0 | 4/5 | -7.52% | +0.05% |

All 40 requests completed. One repeated-context pair ended at different lengths because of EOS and is retained in the records but excluded from equal-length timing. Changing arithmetic can change sampled outputs, so these are end-to-end workload costs rather than identical-work kernel timings. The repeated-context eligible pairs did produce identical token streams (5/5 at 0.6 and 4/4 at 1.0).

The policy has a measurable cost on this host. This PR does not silently enable it or change other modes' numerical policy. Selecting a fixed target arithmetic before prefill and cache construction needs an explicit compatibility/performance decision.

### Remaining validation

Keep this PR draft. The exact tests cover the admission-law bug; the fixed-position frequency pilot remains too small to establish equivalence. The cached-prefix logit/replay checks identify an independent numerical mechanism, but do not close all numerical or state paths.

Two decisions remain: the performance tradeoff of the top default inside probabilistic mode, and the target-arithmetic contract for that mode under #152. Consistent IQ arithmetic is a validation control here, not an automatically applied production change. The RTX 3060, RTX 5070 and 16 GB-card matrix requested in #2028 remains open, as does SYCL build coverage. These short requests do not establish long-request performance.

関連リンク

インストール・モデル・リリースへの站内リンク。