fix(pf-encode): drop the 10-bit short circuit — measured wrong on Ada, twice
WP1.3, and the measurement that justifies it. `resolve_split_mode`'s 10-bit rule sat ABOVE the pixel-rate arm and took no codec, so it (D1) vetoed 10-bit 4K120 -- the very case the pixel-rate arm exists for -- and (D2) applied an HEVC-Main10-on- Ada result to AV1 10-bit, which has no such measurement. Both fixed: the pixel-rate arm now comes first, and what remains is codec-scoped to HEVC and only applies BELOW that bar, where a second engine buys nothing anyway. The rule rested on one datapoint: 5120x1440@240 Main10 on Ada, forced-2 7.6 ms vs 2.8 ms single-engine -- split 2.7x SLOWER. Dropping the short circuit flips that exact configuration's behaviour, so it was re-measured on a 4090 (AD102, driver 610.43.03), 400 Mbps, sub-frame pinned off, via a new mode-parameterizable Main10 A/B test (PF_AB_MODE=WxHxFPS reproduces the original operating point). Ada 4090 single forced-2 ratio 3840x2160@60 4483 us 2178 us 2.06x split WINS 5120x1440@240 3689 us 2813 us 1.31x split WINS <- the veto's origin 3840x2160@120 4148 us 2189 us 1.89x split WINS Blackwell 5070 Ti 3840x2160@60 4216 us 2477 us 1.70x split WINS 5120x1440@240 4651 us 3894 us 1.19x split WINS Split wins for Main10 at every mode on BOTH architectures, including the config the veto came from. The original number does not reproduce. ⚠ Caveats, unchanged from the rest of this work: content is trivial (297-300 B/AU against an 833 KB CBR quota -- zeroed VRAM), so this is the pixel-proportional term and the bits/frame regime is still unmeasured; debug build; and the driver differs from whenever the original was taken. Also validated on Ada in the same session -- the whole spike set reproduces on a SECOND architecture and an OLDER driver (610.43.03 vs 610.57.04): S1a in-place split switch accepted with zero IDRs both directions; S1b takes effect (|C-B|=12 vs |C-A|=1921, the cleanest run yet); S1c pair flip passes; D5 confirmed (AUTO+sub-frame 4424 vs DISABLE 4409, 15 us apart -- and AUTO without sub-frame 2310 ~= TWO_FORCED 2314, so the arm stays); engines=2 with THREE_FORCED correctly clamped to mode 2; arbitration converged with exactly 1 keyframe. Verified: .21 clippy -D warnings clean + 64 unit tests; .133 Windows clippy -D warnings clean (the resolver signature grew a `codec` param, so both backends moved); Ada + Blackwell on-hardware as above. fmt clean.
This commit is contained in:
@@ -1366,7 +1366,7 @@ impl NvencCudaEncoder {
|
||||
// [`resolve_split_mode`] for the precedence (env override / 10-bit / pixel rate).
|
||||
let pixel_rate = self.width as u64 * self.height as u64 * self.fps.max(1) as u64;
|
||||
let mut split_mode: u32 =
|
||||
resolve_split_mode(self.bit_depth, pixel_rate, self.encoder_engines);
|
||||
resolve_split_mode(self.codec, self.bit_depth, pixel_rate, self.encoder_engines);
|
||||
// A verdict this process already measured for this exact config wins over the static
|
||||
// rule — that is the whole point of arbitrating, and it lets later sessions skip the
|
||||
// ~1 s experiment. An operator pin still beats both (checked inside `resolve_split_mode`,
|
||||
@@ -3942,6 +3942,114 @@ mod tests {
|
||||
super::super::nvenc_core::clear_split_verdicts();
|
||||
}
|
||||
|
||||
/// ON-HARDWARE — **THE ADA MAIN10 QUESTION**, the one this whole programme has been deferring.
|
||||
///
|
||||
/// The 10-bit split veto rests on a single datapoint: at 5120×1440@240 Main10 on Ada, forced-2
|
||||
/// took 7.6 ms/frame against 2.8 ms single-engine — split was **2.7× SLOWER**. That number
|
||||
/// vetoed splitting for every HDR session on every GPU, and `resolve_split_mode` has now
|
||||
/// stopped short-circuiting on it, which means a Main10 session above the pixel-rate bar WILL
|
||||
/// split. If the datapoint generalises, that is a regression and the veto has to come back
|
||||
/// (scoped properly this time).
|
||||
///
|
||||
/// So: 4K **Main10** (10-bit, via the packed-RGB10 input path), forced-2 against
|
||||
/// single-engine, same bitrate, sub-frame pinned off so only the split variable moves.
|
||||
/// Reports rather than asserts — both outcomes are legitimate findings and the point is the
|
||||
/// number. Run on the **Ada** box (`.181`) and compare against Blackwell (`.21`):
|
||||
/// cargo test -p pf-encode --features nvenc -- --ignored --test-threads=1 \
|
||||
/// nvenc_cuda_main10_split_ab --nocapture
|
||||
#[test]
|
||||
#[ignore = "requires an NVIDIA GPU + driver — run manually (Ada .181 vs Blackwell .21)"]
|
||||
fn nvenc_cuda_main10_split_ab() {
|
||||
use std::time::Instant;
|
||||
const BPS: u64 = 400_000_000;
|
||||
const WARMUP: u32 = 12;
|
||||
const MEASURED: u32 = 32;
|
||||
// Mode is overridable so the SAME test can be pointed at the configuration the veto was
|
||||
// originally measured on — `PF_AB_MODE=5120x1440x240` reproduces the 2.7×-slower datapoint's
|
||||
// operating point, which is the one config this change flips behaviour for.
|
||||
let (w, h, fps) = std::env::var("PF_AB_MODE")
|
||||
.ok()
|
||||
.and_then(|s| {
|
||||
let p: Vec<u32> = s.split('x').filter_map(|v| v.parse().ok()).collect();
|
||||
(p.len() == 3).then(|| (p[0], p[1], p[2]))
|
||||
})
|
||||
.unwrap_or((3840, 2160, 60));
|
||||
|
||||
std::env::set_var("PUNKTFUNK_NVENC_SUBFRAME", "0");
|
||||
pf_zerocopy::cuda::make_current().expect("shared CUDA context current");
|
||||
// 10-bit input: the packed 2:10:10:10 PQ path is how a Main10 session is actually fed here
|
||||
// (`bit_depth`/`hdr` are DERIVED from the input format, never trusted from the args).
|
||||
let frames: Vec<CapturedFrame> = (0..4).map(|i| rgb10_frame(w, h, i)).collect();
|
||||
|
||||
let run = |split: &str| -> (u128, u8, usize) {
|
||||
std::env::set_var("PUNKTFUNK_SPLIT_ENCODE", split);
|
||||
let mut enc = NvencCudaEncoder::open(
|
||||
Codec::H265,
|
||||
PixelFormat::X2Rgb10,
|
||||
w,
|
||||
h,
|
||||
fps,
|
||||
BPS,
|
||||
true,
|
||||
10,
|
||||
ChromaFormat::Yuv420,
|
||||
false,
|
||||
4,
|
||||
)
|
||||
.expect("open NVENC CUDA session");
|
||||
let (mut times, mut bytes) = (Vec::new(), Vec::new());
|
||||
for i in 0..(WARMUP + MEASURED) {
|
||||
let t0 = Instant::now();
|
||||
enc.submit_indexed(&frames[(i % 4) as usize], i)
|
||||
.expect("submit");
|
||||
let mut got = 0usize;
|
||||
while let Some(au) = enc.poll().expect("poll") {
|
||||
got = au.data.len();
|
||||
}
|
||||
if i >= WARMUP {
|
||||
times.push(t0.elapsed().as_micros());
|
||||
bytes.push(got);
|
||||
}
|
||||
}
|
||||
let depth = enc.bit_depth;
|
||||
let opened = enc.split_mode;
|
||||
enc.flush().ok();
|
||||
times.sort_unstable();
|
||||
bytes.sort_unstable();
|
||||
println!(
|
||||
" (opened split_mode={opened}, derived bit_depth={depth}, \
|
||||
{} B/AU)",
|
||||
bytes[bytes.len() / 2]
|
||||
);
|
||||
(times[times.len() / 2], depth, bytes[bytes.len() / 2])
|
||||
};
|
||||
|
||||
println!(
|
||||
"Main10 split A/B @ {w}x{h}@{fps} HEVC 10-bit, {} Mbps:",
|
||||
BPS / 1_000_000
|
||||
);
|
||||
let (single_us, d1, _) = run("0");
|
||||
println!(" single-engine : {single_us:>6} us/frame");
|
||||
let (split_us, d2, _) = run("2");
|
||||
println!(" forced 2-way : {split_us:>6} us/frame");
|
||||
assert_eq!(d1, 10, "leg 1 did not derive a 10-bit session");
|
||||
assert_eq!(d2, 10, "leg 2 did not derive a 10-bit session");
|
||||
let ratio = single_us as f64 / split_us.max(1) as f64;
|
||||
println!(
|
||||
" ⇒ split is {ratio:.2}× the single-engine rate — {}",
|
||||
if ratio > 1.15 {
|
||||
"split WINS for Main10 here; the 2.7x-slower datapoint does NOT generalise"
|
||||
} else if ratio < 0.87 {
|
||||
"split LOSES for Main10 — the veto was right and must come back, scoped"
|
||||
} else {
|
||||
"a wash; neither arm is clearly better for Main10 here"
|
||||
}
|
||||
);
|
||||
|
||||
std::env::remove_var("PUNKTFUNK_SPLIT_ENCODE");
|
||||
std::env::remove_var("PUNKTFUNK_NVENC_SUBFRAME");
|
||||
}
|
||||
|
||||
/// A pre-session RFI request and nonsense ranges all correctly decline (→ caller forces IDR).
|
||||
/// Needs no GPU session (it short-circuits on the null encoder / range checks), so it runs in the
|
||||
/// normal suite — but `open` gates on the NVENC `.so`, so it skips gracefully where the NVIDIA
|
||||
|
||||
Reference in New Issue
Block a user