You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
TensorRT 10.3 FP16 inference produces incorrect segmentation results for DINOv3 ViT models while TensorRT 8.6.1 works correctly #4723
I trained a semantic segmentation model based on DINOv3 ViT backbone using the Lightly_train framework. The model uses pretrained weights from ViT-B16 and ViT-L16 and is fine-tuned for a segmentation task.
However, I encountered inconsistent inference results when converting the model to TensorRT with different TensorRT versions.
Case 1: ViT-B16 backbone
When converting the model to FP16 TensorRT engines:
TensorRT 8.6.1
Engine builds successfully
FP16 inference results are correct
TensorRT 10.3
Engine also builds successfully
But inference results are incorrect
The predicted segmentation map contains almost a single class value across the entire image
I verified that:
The ONNX model produces correct results
The inference code is identical between TensorRT versions
CNN-based segmentation models run correctly on TensorRT 10.3 using the same pipeline
Therefore, the issue seems specific to Transformer-based models (ViT / DINOv3).
After inspecting the TensorRT engine behavior, I suspect there may be numerical instability or FP16 overflow in TensorRT 10.3, possibly related to attention or LayerNorm operations.
Case 2: ViT-L16 backbone
For a larger model using ViT-L16 weights, the situation is worse.
When converting to FP16 TensorRT engines:
TensorRT 8.6.1
TensorRT 10.3
TensorRT 10.15
All versions build successfully, but inference results are incorrect.
From preliminary analysis, this may be caused by precision overflow or numerical instability in FP16, since ViT-L16 has a deeper Transformer architecture.
Observations
During conversion, I also noticed a difference in attention handling:
In TensorRT 8.6.1, multi-head attention seems to be fused into optimized kernels
In TensorRT 10.3, attention operations appear to be fully decomposed into MatMul / Transpose / Softmax layers
This difference might be related to the observed inference errors.
Questions
1、Why does TensorRT 8.6.1 produce correct results while TensorRT 10.3 produces incorrect results for the same ViT-B16 model?
2、Could this be related to:
numerical instability in FP16
attention decomposition in TensorRT 10.x
LayerNorm precision issues?
3、For deeper Transformer models such as ViT-L16, what is the recommended way to build stable FP16 TensorRT engines?
For example:
Should certain layers (e.g., LayerNorm or Softmax) be forced to FP32?
Are there recommended TensorRT build flags or plugins for Transformer models?
The regression between TRT 8.6.1 and 10.3 for ViT-B16, combined with ViT-L16 failing on all versions, points to FP16 overflow in the attention softmax.
The numerical chain:
Fused MHA (TRT 8.6.1) vs decomposed attention (TRT 10.3). In TRT 8.6.1's fused MHA kernel, the entire softmax(Q*K^T / sqrt(d_k)) * V computation likely uses FP32 accumulators internally. In TRT 10.3's decomposed path, the MatMul(Q, K^T) output is materialized as an FP16 tensor before being fed to a separate Softmax operator.
Where FP16 breaks. For ViT with d_k=64, scaled attention logits typically range from -10 to +10. The softmax numerator computes exp(z_i - z_max). In FP16:
exp(11.09) = 65504 = FP16_MAX
Any logit spread > ~11 causes exp() to overflow to Inf
For ViT-B16 (768-dim, 12 heads), logit spreads occasionally exceed this. For ViT-L16 (1024-dim, 16 heads, 24 layers), deeper layers produce larger activation norms, making overflow nearly guaranteed.
When softmax overflows: numerator = Inf, denominator = Inf, result = NaN. After a few NaN-contaminated attention layers, the output degenerates to a single class.
Why 8.6.1 works for B16 but 10.3 doesn't. The fused kernel computes exp(z_i - z_max) in FP32 internally before writing FP16 output, so the intermediate overflow never happens.
This forces only Softmax layers to FP32 while keeping everything else in FP16. For ViT-L16, you may also need LayerNorm in FP32, since accumulated normalization errors in deeper networks shift the attention logit distribution.
We hit what looks like the same regression with a fine-tuned DINOv2 ViT-B/14 model (keypoint regression head, 392×392 input, 785 tokens) on TensorRT 10.3.0, RTX 3080. The ONNX model was correct in ONNX Runtime, but the TRT engine was badly off: median error ~70 px against ORT.
It wasn't FP16. The engine was just as wrong in FP32, in strongly-typed builds, and at every builder optimization level we tried. That points away from softmax/LayerNorm overflow, at least in our case. If you can, build your ViT-B16 engine in FP32 and compare with ORT. If FP32 is also wrong, this is probably the same bug.
Root cause (found by truncating the graph and comparing intermediate outputs with ORT): the PyTorch/timm export splits q/k/v like this:
qkv [B, N, 3*H*D] -> Reshape [B, N, 3, H, D] -> Split(axis=2) -> Squeeze(axis=2) -> q, k, v [B, N, H, D]
TRT 10.3 (Myelin) computes this Split with the wrong memory layout. In effect it reads [B, N, 3, H, D] as [B, N, H, 3, D], so query head h gets the data of qkv block 3h. Every attention block mixes heads, and the output is garbage, which fits a near-constant segmentation map.
One debugging trap: marking intermediate tensors as extra network outputs changes Myelin's fusion and makes the bug disappear. Truncate the ONNX graph instead.
Workaround: rewrite each such pattern in the ONNX before building. Replace Reshape(5D) → Split → Squeeze with three Slice ops on the last axis ([0:HD], [HD:2HD], [2HD:3HD]), each followed by a Reshape to [B, N, H, D]. The math is the same, and ORT output is bit-exact before and after the rewrite. Results with the rewrite:
Build
Error vs ORT (full-frame px)
Latency
FP32
max 0.66
FP16
median 0.03, max 1.55
3.67 ms (3.66 ms unpatched)
So the FP16 rewrite costs nothing in speed, and FP16 works fine once the layout bug is gone.
Explicit (non-fused) attention or moving the attention scale did not help us. Only the qkv split rewrite fixed it.
ViT-L16 also failing on 8.6.1 may be a separate, genuinely FP16-related problem. For the ViT-B16 regression from 8.6.1 to 10.x, though, I'd check for this qkv Split pattern first. I can share our small ONNX rewrite script if it's useful.
Activity
Whatsonyourmind commentedon Apr 4, 2026
The regression between TRT 8.6.1 and 10.3 for ViT-B16, combined with ViT-L16 failing on all versions, points to FP16 overflow in the attention softmax.
The numerical chain:
Fused MHA (TRT 8.6.1) vs decomposed attention (TRT 10.3). In TRT 8.6.1's fused MHA kernel, the entire
softmax(Q*K^T / sqrt(d_k)) * Vcomputation likely uses FP32 accumulators internally. In TRT 10.3's decomposed path, theMatMul(Q, K^T)output is materialized as an FP16 tensor before being fed to a separate Softmax operator.Where FP16 breaks. For ViT with d_k=64, scaled attention logits typically range from -10 to +10. The softmax numerator computes
exp(z_i - z_max). In FP16:exp(11.09) = 65504= FP16_MAXexp()to overflow to InfFor ViT-B16 (768-dim, 12 heads), logit spreads occasionally exceed this. For ViT-L16 (1024-dim, 16 heads, 24 layers), deeper layers produce larger activation norms, making overflow nearly guaranteed.
When softmax overflows: numerator = Inf, denominator = Inf, result = NaN. After a few NaN-contaminated attention layers, the output degenerates to a single class.
Why 8.6.1 works for B16 but 10.3 doesn't. The fused kernel computes
exp(z_i - z_max)in FP32 internally before writing FP16 output, so the intermediate overflow never happens.Recommended fix (minimal precision override):
This forces only Softmax layers to FP32 while keeping everything else in FP16. For ViT-L16, you may also need LayerNorm in FP32, since accumulated normalization errors in deeper networks shift the attention logit distribution.
elymsyr commentedon Oct 19, 2026
We hit what looks like the same regression with a fine-tuned DINOv2 ViT-B/14 model (keypoint regression head, 392×392 input, 785 tokens) on TensorRT 10.3.0, RTX 3080. The ONNX model was correct in ONNX Runtime, but the TRT engine was badly off: median error ~70 px against ORT.
It wasn't FP16. The engine was just as wrong in FP32, in strongly-typed builds, and at every builder optimization level we tried. That points away from softmax/LayerNorm overflow, at least in our case. If you can, build your ViT-B16 engine in FP32 and compare with ORT. If FP32 is also wrong, this is probably the same bug.
Root cause (found by truncating the graph and comparing intermediate outputs with ORT): the PyTorch/timm export splits q/k/v like this:
TRT 10.3 (Myelin) computes this Split with the wrong memory layout. In effect it reads
[B, N, 3, H, D]as[B, N, H, 3, D], so query headhgets the data of qkv block3h. Every attention block mixes heads, and the output is garbage, which fits a near-constant segmentation map.One debugging trap: marking intermediate tensors as extra network outputs changes Myelin's fusion and makes the bug disappear. Truncate the ONNX graph instead.
Workaround: rewrite each such pattern in the ONNX before building. Replace
Reshape(5D) → Split → Squeezewith threeSliceops on the last axis ([0:HD],[HD:2HD],[2HD:3HD]), each followed by aReshapeto[B, N, H, D]. The math is the same, and ORT output is bit-exact before and after the rewrite. Results with the rewrite:So the FP16 rewrite costs nothing in speed, and FP16 works fine once the layout bug is gone.
Explicit (non-fused) attention or moving the attention scale did not help us. Only the qkv split rewrite fixed it.
ViT-L16 also failing on 8.6.1 may be a separate, genuinely FP16-related problem. For the ViT-B16 regression from 8.6.1 to 10.x, though, I'd check for this qkv Split pattern first. I can share our small ONNX rewrite script if it's useful.
fix_vit_qkv_split.py