Skip to content

TensorRT 10.3 FP16 inference produces incorrect segmentation results for DINOv3 ViT models while TensorRT 8.6.1 works correctly #4723

Open

Description

@dingliangxiansheng

Description

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:

  1. In TensorRT 8.6.1, multi-head attention seems to be fused into optimized kernels
  2. 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?

Any guidance would be greatly appreciated.

Activity

  1. Whatsonyourmind commented on Apr 4, 2026

    @Whatsonyourmind

    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:

    1. 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.

    2. 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.

    3. When softmax overflows: numerator = Inf, denominator = Inf, result = NaN. After a few NaN-contaminated attention layers, the output degenerates to a single class.

    4. 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):

    config.set_flag(trt.BuilderFlag.OBEY_PRECISION_CONSTRAINTS)
    for i in range(network.num_layers):
        layer = network.get_layer(i)
        if layer.type == trt.LayerType.SOFTMAX:
            layer.precision = trt.float32
            layer.set_output_type(0, trt.float32)

    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.

  2. elymsyr commented on Oct 19, 2026

    @elymsyr

    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.

    fix_vit_qkv_split.py

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Module:AccuracyOutput mismatch between TensorRT and other frameworks

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions