Repository navigation
fix+chore: 30+ fixes bundle F — deferred criticals + MCP+round5 bulk - #859
Merged
Merged
Conversation
…kernels gfx10+ (RDNA2+) devices run in wave32 mode; all HIP kernels that hardcoded WARP_SIZE=64 produced wrong results (incorrect warp-reduce strides, lane detection off by factor 2, warp-partial shared arrays under-allocated on wave32 hardware) because the reduction loops iterated 32 lanes when only 16 were live. Fix pattern applied across 10 kernel files: - Replace `#define *_WARP_SIZE 64` with `*_MIN_WARP_SIZE 32` (for shared-mem sizing — allocates enough slots for wave32 worst case). - Replace `WARPS_PER_BLOCK = BX*BY / WARP_SIZE` denominator with MIN_WARP_SIZE so shared-mem arrays are large enough on wave32 (over-allocated on wave64, which is correct and safe — unused slots are never written). - Replace all `for (off = WARP_SIZE/2; ...)` with `(int)warpSize / 2`. - Replace all `lid % WARP_SIZE` / `lid / WARP_SIZE` with `% (unsigned)warpSize` / `/ (unsigned)warpSize`. - Replace compile-time `WARPS_PER_BLOCK` loop bounds in the final accumulation loops with runtime `nwarps = blockDim.x * blockDim.y / warpSize` so wave64 does not read uninitialised shared-mem slots 2-3 that wave32 would write. Files changed (9 kernels + 2 host launchers): float_adm_score.hip, float_ssim/ssim_score.hip, integer_ciede/ciede_score.hip, integer_moment/moment_score.hip, integer_motion/motion_score.hip, integer_motion_v2/motion_v2_score.hip, integer_ms_ssim/ms_ssim_score.hip, integer_psnr/psnr_score.hip, integer_ssim/integer_ssim_score.hip. speed/speed_score.hip: HIP_WARP_SIZE is a launch-time parameter (blockDim.x must equal the device wavefront size). Updated speed_chroma_hip.c and speed_temporal_hip.c to query hipDeviceProp_t.warpSize at init and store it in state->solve_warp, replacing the hardcoded SC_SOLVE_WARP/ST_SOLVE_WARP constant at hipModuleLaunchKernel call sites. integer_vif/vif_statistics.hip was already fixed in PR #850 (ADR-0563 per-thread atomicAdd); float_motion, float_psnr, float_vif, float_moment were already using warpSize at runtime. Local verify: meson setup -Denable_hip=true + ninja 1087/1087 OK. The 9 fast+gpu HIP parity failures are pre-existing on master (confirmed by baseline run on origin/master before this change). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The vmaf_tiny_v2/v3/v4 ONNX graphs bake the StandardScaler as Constant
nodes (ADR-0244), so the runtime should feed raw feature values. However,
the sidecar JSON files echo input_mean/input_std for tooling, and
vmaf_dnn_sidecar_load sets has_feature_scaler=true whenever it finds
those arrays. The C runtime in vmaf_ctx_dnn_run_frame_feature_vector then
unconditionally applied (v - mean) / std before calling ORT — effectively
double-scaling every feature vector and corrupting vmaf_tiny_v{2,3,4}
scores.
Fix: add an onnx_has_scaler boolean field to VmafModelSidecar and the
sidecar JSON spec. When true, the C runtime skips its own scaler step
because the ONNX graph handles normalisation internally. The three
affected sidecars (vmaf_tiny_v2.json, v3.json, v4.json) are updated to
set "onnx_has_scaler": true. Older models without this flag (fr_regressor
v1/v2/v3 etc.) are unaffected — their ONNX graphs do NOT bake the scaler,
so the C runtime continues to apply it.
Two new unit tests are added to test_model_loader.c:
- test_sidecar_onnx_has_scaler_flag: verifies parsing onnx_has_scaler
while has_feature_scaler remains true for tooling.
- test_sidecar_onnx_has_scaler_absent: verifies backward compatibility
(flag defaults to false when key is absent from the sidecar).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… failure vmaf_gpu_picture_pool_init accumulated errors with |= but never freed pictures that were successfully allocated before the first failure, leaking one GPU buffer per successfully-completed slot. Extract alloc_pictures() static helper that breaks on first failure and rolls back slots 0..alloc_cnt-1 via free_picture_callback before returning the error. Also call pthread_mutex_destroy on the alloc-failure path so the mutex does not leak its internal state. The helper also brings vmaf_gpu_picture_pool_init back under the 60-line readability-function-size threshold (was 64 lines). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ate failure vmaf_feature_extractor_context_create freed and returned *fex_ctx as a dangling pointer on error (malloc failure or vmaf_fex_ctx_parse_options failure), without zeroing the out-parameter. Two call sites in libvmaf.c then used err |= set_fex_cuda_state(fex_ctx, ...) unconditionally, dereferencing the freed pointer before the err-check. Fix in three parts: 1. feature_extractor.c / .cpp: set *fex_ctx = NULL on all error exits so callers always receive NULL on failure, never a dangling pointer. 2. libvmaf.c (vmaf_use_feature_extractor): early-return on create failure before any set_fex_*() call; also destroy fex_ctx on state-setter failure. 3. libvmaf.c (vmaf_use_features_from_model): same pattern. The pool slot allocator (ctx_pool_claim_slot) and predict.c already handled the error correctly; no change needed there. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
vmaf_use_features_from_model stores the raw VmafModel* inside the context's feature-collector linked list (vmaf_feature_collector_mount_model in core/src/feature/feature_collector.c, line 272: m->model = model). Libvmaf dereferences that pointer during score computation, so if the Rust Model wrapper was dropped while the Context was still alive, every subsequent vmaf_score_pooled / vmaf_score_at_index call would be a use-after-free. Add lifetime parameter 'a to Context<'a> with PhantomData<&'a mut Model> (invariant over 'a to prevent coercion to a longer lifetime), and bind use_features_from_model to model: &'a mut Model. A freshly-constructed Context<'static> carries no borrow; the borrow checker narrows the context's apparent lifetime to the shortest-lived registered model as soon as use_features_from_model is called, preventing the context from outliving any registered model. ContextBuilder::build() explicitly returns Result<Context<'static>>. Local-verify: cargo check + cargo clippy -D warnings, both clean. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When `vmaf_read_pictures` returns an error, it has not taken ownership of the plane buffers. The Rust-side `Picture` wrappers already had their `owned` flag cleared by `into_raw_owned()`, so their `Drop` became a no-op. The resulting raw `VmafPicture` structs then fell out of scope without being freed — a silent memory leak on every rejected frame. Fix: check `rc < 0` immediately after the FFI call and explicitly call `vmaf_picture_unref` on both raw pictures before returning the error. Added a unit test (`into_raw_owned_clears_owned_flag_and_caller_frees`) in `picture.rs` that exercises the same manual-unref path. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ase images Four Dockerfiles referenced golang:1.23-bookworm (controller, node, operator) and nvidia/cuda:13.3.0-devel-ubuntu24.04 (root Dockerfile) without @sha256 digest anchors, leaving supply-chain provenance unverifiable and builds non-reproducible across rebuilds. Pin each with the current manifest digest so BuildKit verifies the layer before pulling. Also removes the stale TODO comment in Dockerfile that deferred the nvidia/cuda pin until the digest "stabilises". - Dockerfile: nvidia/cuda:13.3.0-devel-ubuntu24.04@sha256:ef220... - docker/Dockerfile.controller: golang:1.23-bookworm@sha256:16705... - docker/Dockerfile.node: golang:1.23-bookworm@sha256:16705... - docker/Dockerfile.operator: golang:1.23-bookworm@sha256:16705... Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… manifest The five per-member sha256 fields in the ensemble manifest were out of sync with the actual ONNX files on disk. The per-seed sidecar JSONs already carry the correct hashes (verified); only the parent manifest was stale. Updated fields (old → new): seed0: fa89df1b... → 08ab1aed... seed1: 48b0b9f3... → 42d86249... seed2: 927a23fe... → 0f1d8a81... seed3: 1be056b0... → ed5e9260... seed4: 584ad69b... → 036c6224... Verification: sha256sum of each .onnx file matches the updated manifest entry. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1. round2_client fixture: extend VMAFX_MCP_HTTP_NO_AUTH patch to cover the full test duration (yield inside with-block) so the security middleware sees the bypass at request-dispatch time, not just at app-creation time. 2. test_vmaf_version_handles_version_timeout: drive async _vmaf_version() via asyncio.run() instead of calling it synchronously (ADR-1023 made the function async). 3. test_coverage_round3.py HTTP tests: extend patch.dict scope to wrap client.post() + assertions in all three tests; the with-block was exiting before the request was made. 4. test_describe_model_onnx_no_metadata: replace hardcoded REPO constant with monkeypatch.setattr(srv, '_repo_root', lambda: tmp_path) to decouple from the physical install layout. 5. test_describe_worst_frames_uses_unique_tempdir_per_call: update assertion to match TemporaryDirectory semantics (files cleaned up on context exit — assert NOT exists, not exists). All 8 previously-failing tests now pass (0.44 s). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…Rust/warnings bulk
18 files, 19 distinct fixes across 11 of the 12 round-5 hunt cells
(race-conditions cell handled separately):
memory-leaks-strict (cell 2):
- core/src/libvmaf.c: set *vmaf=NULL after free(v) in free_v: path to
prevent dangling pointer after vmaf_init sub-init failure
- core/src/thread_pool.c: set *pool=NULL after free(p) on workers-malloc
failure; change if→while for pthread_cond_wait to guard spurious wakeups
- core/src/feature/feature_extractor.c: set *pool=NULL after free(p) at
free_p: label; add comment documenting intentional mutex-init ordering
tiny-ai-model-runtime-audit (cell 9):
- core/src/pdjson.c: fix UBSan SIGABRT — change stack_top=-1 to
(size_t)-1 at both init sites, matching the (size_t)-1 sentinel used
at the two comparison sites (lines 720/746)
cross-extractor-numeric-comparison (cell 5):
- core/src/feature/x86/motion_avx512.c: fix signed integer overflow in
three 16-bit scalar-tail paths (y_conv_8, y_conv_16, x_conv_16) — cast
uint16_t filter[k] and src pixels to uint32_t before multiply so
products stay in unsigned arithmetic (UBSan SIGABRT fix)
hidden-config-options (cell 6):
- core/meson_options.txt: fix inverted enable_nvcc description (was "Use
clang…"; corrected to "Use nvcc…; when false, clang is used")
- docs/development/build-flags.md: remove false claim that enable_vulkan
remains as a failing stub; the option() was fully deleted per ADR-0726
- core/src/meson.build: remove dead -DOC_NEW_STYLE_INCLUDES legacy define
(no C consumer in tree since upstream libogg era)
- core/src/feature/adm.c: collapse ADM_OPT_SINGLE_PRECISION #ifdef branch
(symbol never defined anywhere; 1e-2 threshold was permanently dead code)
unused-code-dead-code (cell 7):
- core/src/picture.h: remove dead VMAF_PICTURE_BUFFER_TYPE_VULKAN_DEVICE
enum value (Vulkan removed per ADR-0726; no reference sites remain)
meson-build-warnings-grind (cell 12):
- core/src/opt.h: char*→const char* for default_val.s to prevent
write-to-string-literal UB (icpx -Wwritable-strings)
- core/src/opt.c + opt.cpp: propagate const through set_option_string
signature; opt.cpp uses const_cast per ADR-0721 ABI-stability comment
- core/src/feature/sycl/sycl_compat.h: update VMAF_SYCL_REQD_SG_SIZE macro
from deprecated [[intel::reqd_sub_group_size(N)]] to SYCL 2020 standard
[[sycl::reqd_sub_group_size(N)]] (oneAPI 2026.0 deprecation warning)
go-pkg-error-handling-audit (cell 10):
- pkg/libvmaf/libvmaf.go: check tmpOut.Close() error; use logged deferred
remove instead of bare defer os.Remove
- cmd/vmafx-controller/queue/queue.go: surface json.Marshal error in
ReportResult instead of silently falling back to "{}"; fix ListAll
defer rows.Close() to log close errors (matching reload() pattern)
- cmd/vmafx-mcp/impl.go: intArg/floatArg return def (not 0) on
json.Number conversion failure
rust-bindings-safety (cell 11):
- bindings/rust/vmafx-sys/tests/integration_test.rs: add debug_assert for
non-negative strides before as-usize cast; add allow(cast_sign_loss)
with justification comment
Build verified: ninja -C /tmp/vmaf-round5-build (CPU-only) 894/894 OK,
meson test --suite=fast 87/87 OK (including test_motion_avx512_parity
which previously aborted under UBSan). go build clean. cargo check clean.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
lusoris
marked this pull request as ready for review
June 12, 2026 17:38
lusoris
force-pushed
the
chore/bundle-f-30-fixes-deferred-mcp-bulk-race
branch
from
June 12, 2026 17:38
2040251 to
b3d34cc
Compare
This was referenced Jun 12, 2026
lusoris
added a commit
that referenced
this pull request
Jun 12, 2026
…RF, AI scripts, MCP conformance) (#865) * fix(hip,sycl): stale wave32 comment + Kahan IIR blur for ssimulacra2 (iter6-cross-backend-parity) Three iter6 cross-backend-parity findings: 1. [critical — partial] float_adm_score.hip: wave32 code was already fixed in #859 (0a9dba8); correct the lingering stale comment that still read "FADM_WARPS_PER_BLOCK = 4 (64-lane warps)" — FADM_WARPS_PER_BLOCK is now 256/32 = 8 slots (sized for the wave32 worst case). No functional change. 2. [high — already fixed] float_ssim/ssim_score.hip + integer_psnr/psnr_score.hip: wave32 runtime-warpSize fixes landed in #859 alongside float_adm; nothing further to do here. 3. [high] ssimulacra2_sycl.cpp: add Kahan (compensated-summation) state tracking to the 3-pole recursive IIR blur kernel (launch_blur<PASS>). The IIR state (prev1_k) accumulated O(eps) rounding error per step; over 4K-tall frames this exceeded the 5e-5 cross-backend parity contract vs the CPU reference. Each pole now carries a float comp_k compensation term; the standard Kahan pattern (y = candidate - comp; new_state = old_state + y; comp = (new_state - old_state) - y) bounds per-iteration error to O(eps^2) without fp64 (ADR-0220 compliant). No Netflix golden-data assertions modified. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ubsan): replace 0xFFFFFFFFFFFFFFFF hex literals with -1LL in adm_avx512.c _mm512_set_epi64 takes long long (signed 64-bit) arguments. The literal 0xFFFFFFFFFFFFFFFF exceeds LLONG_MAX and is undefined behaviour under strict UBSan. Replace with -1LL which has identical bit pattern and correct type at both call sites (ADM_CM_THRESH_S_I_END macro lines 406 and 698). Finding: iter6-ubsan-strict high [adm_avx512: 0xFFFF... overflows long long]. Note: motion_avx512.c and adm_avx2.c analogous fixes were already present on master (PR #858 / PR #859 bundles). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(thread-safety): eliminate 2 TSAN races in threaded batch path (iter6-tsan-race-deep) Finding #1 (high): Remove the racy write `fex->framesync = vmaf->framesync` in `threaded_enqueue_one`. `fex` is the *shared* registered VmafFeatureExtractor — worker threads from previous frames may concurrently read its fields. The write is redundant: framesync is already propagated to every pool-slot copy by `set_fex_framesync()` at registration time and by `ctx_pool_ensure_slot_ctx()`. Finding #2 (high): Move the `vmaf->prev_ref` advance to BEFORE the enqueue call in `threaded_read_pictures_batch`. In the old order the main thread unreffed and replaced `vmaf->prev_ref` after enqueue while the just-submitted worker still held a live reference to the same underlying VmafRef*, creating a concurrent unref/write on the same object without synchronisation. Workers use `data.prev_ref` (an independently refcounted snapshot) exclusively and never re-read `vmaf->prev_ref` after enqueue, so moving the advance before enqueue is both safe and race-free. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(vmaf-tune): fix --no-bisect TypeError and recommend CRF selection strategy Two bugs in the compare/recommend paths: 1. _encode_and_score() in bisect.py had encode_runner and score_runner as required keyword-only args (no defaults). The CRF-sweep caller in cli.py did not pass them, causing an unconditional TypeError on any compare --no-bisect --crf-sweep invocation. Fixed by giving both parameters a default of None, consistent with the existing decode_runner parameter and the run_encode/run_score runner=None semantics. 2. _smallest_passing_crf() in cli.py used `crf > cur[0]` to select the largest (most efficient) passing CRF, contradicting both the function name and the CLI help string ("find the smallest CRF whose VMAF >= --target-vmaf"). The correct strategy is the smallest (highest-quality) passing CRF. Fixed comparison to `crf < cur[0]` and updated the docstring to match. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ai-scripts): 3 iter6 runtime bugs — vulkan-device AttributeError, saliency parity always-fail, ensemble ONNX codec-dim mismatch - collect_gpu_calibration_data.py: remove dead args.vulkan_device reference from devices dict (Vulkan removed per ADR-0726; no --vulkan-device argparse registration existed, causing AttributeError at runtime) - validate_saliency_student.py: replace broken PT-reconstruction parity check with ORT-only sanity check when no PT state provided. do_constant_folding=True folds BN stats into conv weights at export so ONNX initializer names diverge from PT state_dict keys; the old code silently left 60 of 65 weights at random defaults and always failed. When pt_state is provided (trainer path) full PT<->ORT diff is still performed. - model/tiny/fr_regressor_v2_ensemble_v1_seed{0..4}.onnx: regenerate with codec_onehot=[batch,6] matching current CODEC_VOCAB (was [batch,14] from a 12-entry encoder_vocab + 2 norm dims; CODEC_VOCAB was later trimmed to 6). Regenerated via train_fr_regressor_v2_ensemble.py --smoke in vmaf-dev-mcp container. eval_probabilistic_proxy.py --smoke now passes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(mcp): JSON-RPC parse-error conformance + deep-nesting guard Two iter6 fuzz conformance findings fixed: 1. [high] Parse-error notification instead of error response (id=null): Add `_ParseErrorFilteredStdin` — a lazy async stdin wrapper that pre-validates each incoming line with `JSONRPCMessage.model_validate_json`. On failure it calls `_emit_parse_error` which writes `{"jsonrpc":"2.0","id":<recovered_or_null>,"error":{"code":-32700, "message":"Parse error"}}` to stdout synchronously, then drops the line so the mcp library never sees a bare Exception on the stream (the notification path is bypassed entirely). `_run()` now passes `stdin=_ParseErrorFilteredStdin()` to `stdio_server`. 2. [high] 500-level deep nesting triggers recursion-limit exception: Add `_check_depth(obj, max_depth=50)` helper and call it at the top of `_call_tool_dispatch` before the tool dispatch. Payloads exceeding 50 nesting levels raise `ValueError`, which the mcp library converts to an isError=True tool result before the pydantic parser recurses. Existing tests updated: the two `stdio_server`-patching tests in `test_coverage_round2.py` now accept `**kwargs` so they tolerate the new `stdin=` keyword argument forwarded by `_run()`. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Lusoris <lusoris@pm.me> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
9 tasks done
6 of 7 tasks
10 of 13 tasks
lusoris
added a commit
that referenced
this pull request
Sep 5, 2026
…1282) * docs(dev): replace the impossible venv recipe with the verified one Replace the impossible repo-root pip install -e ".[dev]" in docs/development/languages.md with the verified per-package editable install recipe. The repository root pyproject.toml contains only tool configuration for vmaf-fork-tooling and lacks build-system and root project dependencies, causing flat-layout discovery failure. Per-package editable installs are the model across independent distributions, with dev/Containerfile:1037-1047 as the authoritative reference. Pin meson==1.12.0 to prevent breaking ninja regeneration. Add recovery instructions for virtualenvs broken by legacy tracked .venv symlink loops (PR #1280). docs/state.md: no bug row needed (docs-only). docs/rebase-notes.md: no rebase impact: docs/development/ is fork-added. * docs(dev): rust.md no longer calls the shipped vmafx crate 'planned' bindings/rust/vmafx has existed since ADR-0929 (PR #859); the page still said a higher-level crate was planned for a future PR. Found by the 2026-09-04 gap triage (#1270), verdict STALE, verified against master. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * docs(changelog): rename the venv-recipe fragment so the tracked-venv gate stops matching it scripts/ci/check-no-tracked-venv.sh matches any basename starting with 'venv' (the dot is optional in its pattern); the fragment name venv-recipe-docs.md tripped the required Pre-Commit check on #1282. The gate itself is tightened in a separate PR. --------- Co-authored-by: Lusoris <lusoris@pm.me> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Mega-bundle of ~30 fixes across three rounds (deferred criticals, MCP test suite, round-5 bulk hunt):
Deferred criticals (rounds 4-5 skip-pile, 8 commits):
warpSizeacross 10 kernel files for gfx10+/wave32 correctnessvmaf_tiny_v2/v3/v4double-scaling fix viaonnx_has_scalersidecar flag; C-side scaler skipped when ONNX has one baked ingpu_picture_poolpartial-alloc rollback: extractedalloc_pictures()helper with mutex + slot cleanup on failurevmaf_use_feature_extractor/vmaf_use_features_from_modelNULL-deref guard:*fex_ctx=NULLon all error exits + early-return in caller + destroy on state-setter failure (3 files)Context<'a>+PhantomData<&'a mut Model>prevents UAF when registered model is freed before contextvmaf_read_picturesFFI: explicitvmaf_picture_unrefonrc<0early-error path to free plane buffersnvidia/cuda:13.3.0andgolang:1.23-bookworm(4 Dockerfiles)fr_regressor_v2_ensemble_v1.jsonSHA256 refresh — 5 stale digests updated to match on-disk ONNX filesMCP test fixes (1 commit, 5 fixes):
round2_clientenv scope, async/sync wrapper,round3patch scope,REPOpath monkeypatch, tempdir cleanup assertionRound-5 bulk (1 commit, 18 fixes): pdjson SIGABRT depth-overflow guard, thread_pool POSIX
while→ifspurious-wakeup fix, libvmaf + thread_pool NULL guards,motion_avx512UBSan cast,opt.hconst-correctness, SYCLreqd_sub_group_size, Gopkg/errorsmigration, Rust assertion fixes, build flags, dead-code removalRace-fix commit (w7nry8u3o) not included — task output was empty (still in flight).
Test plan
meson test -C build --suite=fast: 87/87 PASScargo check -p vmafx: cleangit grep '^<<<<<<<'clean)Deep-dive deliverables (ADR-0108)
meson test --suite=fast,cargo check, ONNX sha256sum cross-check)state.md touch
🤖 Generated with Claude Code