Add ViLT (dandelin/vilt-b32-finetuned-vqa) visual-question-answering support#951
Add ViLT (dandelin/vilt-b32-finetuned-vqa) visual-question-answering support#951ssss141414 wants to merge 4 commits into
Conversation
…support Adds OnnxConfig + ModelPatcher for ViLT visual-question-answering since vendor optimum coverage is absent and stock ViltEmbeddings.visual_embed is not ONNX-traceable (Python iteration over tensor shapes, torch.multinomial, per-row nonzero loops). Patcher swaps in a static-shape replacement using nn.functional.interpolate for spatial position embeddings and a synthesized all-ones token mask. H/W axes are pinned static; pixel_mask is intentionally dropped since the patched path does not reference it. Validated on dandelin/vilt-b32-finetuned-vqa @ CPU fp32: - L0 build: 62.9s, 449.2 MB optimized ONNX - L1 perf: p50=65.83ms, throughput=14.82 samples/sec (20 iters, warmup 3) - L2 numerics: cos=1.000000, max_abs_diff=4.2e-5, top-class match (3129-way head)
Reviewer verification: OV cpu / gpu / npu — branch \shzhen/add-vilt-vqa\Commands\\powershell configuv run winml config -m dandelin/vilt-b32-finetuned-vqa --task visual-question-answering -o temp/verify_pr951_vilt_config.json build (OV CPU, fp32, using recipe)uv run winml build -c examples/recipes/dandelin_vilt-b32-finetuned-vqa/visual-question-answering_config.json -m dandelin/vilt-b32-finetuned-vqa -o temp/verify_pr951_vilt_build --ep openvino --device cpu --precision fp32 --no-quant --no-compile --rebuild perf — cpu / gpu / npu (from built ONNX, 5 iters + 2 warmup)uv run winml perf -m temp/verify_pr951_vilt_build/model.onnx --ep openvino --device cpu --iterations 5 --warmup 2 --skip-build -f json eval schema checkuv run winml eval --schema --task visual-question-answering Results
Notes:
|
|
Validation results (2026-06-25) for PR #951 on this Windows ARM64 host. Scope
Main branch baseline (before PR)
PR #951 branch
Conclusion
|
|
ADDENDUM: main branch baseline (NO support) On current \main\ @ HEAD: Conclusion: This PR introduces vilt support. The \�ilt.py\ source changes (custom \ViltVqaOnnxConfig\ + _ViltVisualEmbedPatcher) are necessary and not catalog-only. All OV devices now pass config/build/perf validation. |
Cover the ViltVqaOnnxConfig contract weightlessly via resolve_io_specs: registration for vilt/visual-question-answering, exactly the 4 declared inputs (pixel_mask deliberately dropped), single logits output, static 384x384 pixel_values, and the MODEL_CLASS_MAPPING -> ViltForQuestionAnswering binding. 6 passed.
reviewer verdict — APPROVE (draft; awaiting human ready-promotion)Independent re-march of the checklist against the pushed producer fix (
Coverage scope (honest annotation): this contribution is verified at the OnnxConfig-contract / unit-test level. Verdict: APPROVE. Left as draft per contributor request — promote with |
reviewer verdict — CORRECTION + real Goal-ladder marchMy earlier verdict on this PR only cited a pytest unit-test run. That was a process failure on my side: unit tests verify the
Author's claim was Coverage: Plus the unit-test contract coverage from before (6 passed). Verdict stands: APPROVE (draft; promote with |
…1,2], honest square-384 constraint)
ssss141414
left a comment
There was a problem hiding this comment.
Reviewer verdict (independent second-host re-verification): APPROVE
Role note: posted as a review comment because GitHub disallows formally approving one's own PR. Re-verification ran on a different host (with a DirectML GPU) from a clean rebuild.
- Value fidelity: the appended section adds DML rows only and does not restate the original CPU numbers as mine; the CPU latency delta (288.28ms vs original 67.49ms) is explicitly attributed to different hardware.
- Load-bearing check re-run: ViLT's risk is the export-time
_ViltVisualEmbedPatcher. L2 numerical parity PASSES on both CPU and DML (cos=1.0, argmax=3 on both), confirming the baked-in patched graph is EP-agnostic and not silently broken on GPU. - L0/L1 re-run: build converges; both EPs run to completion;
pixel_maskcorrectly dropped.
Coverage annotation:
- reachable-verified:
CPUExecutionProvider,DmlExecutionProvider - deferred (host-limited, not a defect):
QNNExecutionProvider/NPU (no NPU on this host),OpenVINOExecutionProvider(present, not exercised), generative-VQA path (out of scope for this checkpoint)
Terminal state: APPROVE · coverage: partial (CPU+DML verified; QNN/NPU + OpenVINO deferred).
ssss141414
left a comment
There was a problem hiding this comment.
Reviewer verdict — OpenVINO EP-coverage completion (2026-07-10)
Correcting my earlier "host-blocked" label: this host (Intel Lunar Lake) reaches its NPU+GPU through the downloadable OpenVINOExecutionProvider v1.8.80.0. Re-ran the EP flow on all three OpenVINO device targets.
ViLT VQA (#951) — APPROVE (strengthened). L1 PASS on OpenVINO NPU, GPU, and CPU with correct logits[1,3129]. NPU 15.42ms / 64.84 samples/sec (slightly faster than GPU 18.04ms). Key point: the export-time _ViltVisualEmbedPatcher is baked into the graph, so the fix is EP-agnostic — it inherits correctly on OpenVINO NPU/GPU with no EP-specific patching.
Reachable-EP coverage now verified: CPU + DML(GPU) + OpenVINO(NPU/GPU/CPU) — all PASS. Only N/A: QNN (Intel silicon).
No code changes requested.
… (_meta-058); duplicate across both validated buckets
Summary
Adds first-class support for ViLT under the
visual-question-answeringtask, validated ondandelin/vilt-b32-finetuned-vqa.ViLT has no vendor optimum coverage, and its stock
ViltEmbeddings.visual_embedis fundamentally not ONNX-traceable (Python iteration over tensor shapes,torch.multinomial, per-rownonzero()loops). Eager works because the loops resolve concretely; tracing fails. This PR therefore ships:ViltVqaOnnxConfig(OnnxConfig)registered via@register_onnx_overwrite("vilt", "visual-question-answering")._ViltVisualEmbedPatcher(ModelPatcher)that swapsvisual_embedfor a static-shape replacement usingnn.functional.interpolate(spatial_pos, size=(H, W), mode='bilinear', align_corners=True)and a synthesized all-ones token mask.pixel_values;pixel_maskis intentionally omitted from the export signature since the patched path doesn't read it (leaving it in would create a dead graph input).models/hf/__init__.py.Files changed
src/winml/modelkit/models/hf/vilt.pysrc/winml/modelkit/models/hf/__init__.pyexamples/recipes/dandelin_vilt-b32-finetuned-vqa/visual-question-answering_config.jsonexamples/recipes/README.mdValidation (dandelin/vilt-b32-finetuned-vqa @ CPU fp32)
Notes for reviewers
input_ids,attention_mask,token_type_ids(dynamicbatch_size/sequence_length),pixel_values(onlybatch_sizedynamic, H/W=384 static).logits(3129-way),batch_sizedynamic.value_rangefor mask-of-ones inputs must be[1, 2]not[0, 1]becauserandinthighis exclusive — relevant if anyone re-derives this recipe.EP-coverage update — DirectML validated on a second host (2026-07-10)
The original submission was verified on a host without a DirectML GPU, so the DML EP was left deferred (
not_yet_tested_on: @ dml-gpu (host-blocked)in theviltfinding). A second contributor re-ran the full Goal-ladder on a machine exposingonnxruntime.get_available_providers() == ['DmlExecutionProvider', 'CPUExecutionProvider'], closing that deferred coverage. No code change. This is significant for ViLT specifically because the custom_ViltVisualEmbedPatcheris applied at export time — this run confirms the baked-in patched graph runs correctly under DML with no EP-specific handling.Per-(EP, device) matrix —
dandelin/vilt-b32-finetuned-vqa @ visual-question-answering @ fp32input_ids/attention_mask/token_type_ids[1,40]int32 +pixel_values[1,3,384,384]f32 →logits[1,3129], 449.1 MB external data co-located;pixel_maskdropped as designedvisual-question-answeringnot in winml eval registryNotes (honesty): the L1 CPU latency here (288.28ms) differs from the original submission's 67.49ms because it is different hardware — both valid, not the same machine. The DML rows are net-new coverage. The load-bearing L2 parity check (which would catch a broken patcher) passes on both EPs with identical argmax.
Coverage after this update: reachable-verified = CPU + DML. Still deferred = QNN/NPU (no NPU on this host), OpenVINO (present but not exercised), and the generative-VQA path (Pix2Struct).
Reproduce DML:
OpenVINO EP matrix — Intel NPU + GPU + CPU (2026-07-10, follow-up)
Correction to the earlier EP-coverage note: this host is an Intel Core Ultra 7 258V (Lunar Lake) — Intel AI Boost NPU, Intel Arc 140V GPU, CPU.
onnxruntime-windowsmlauto-installs OpenVINOExecutionProvider v1.8.80.0 (NPU / GPU / CPU), driven viawinml perf --ep openvino. (Stock ORT only enumerates DML+CPU; the OpenVINO EP lives in the Windows ML EP infrastructure.)L1 perf —
dandelin/vilt-b32-finetuned-vqa @ visual-question-answering @ fp32(20 iters, warmup 5)All three PASS with correct
logits[1,3129]. The export-time_ViltVisualEmbedPatcheris baked into the graph, so OpenVINO NPU/GPU inherit correct behavior with no EP-specific patch — the fix is EP-agnostic across CPU / DML / OpenVINO(NPU/GPU/CPU). NPU is slightly faster than GPU for this model.Full EP coverage on this host: CPU, DML(GPU), OpenVINO(NPU/GPU/CPU) — all PASS. Only N/A: QNN (Intel silicon).
Reproduce: