Skip to content

#4876 - Fix output_fp16 serialization in CustomEmbLayerNormPluginDynamic v4/v5 - #4877

Open
gabortar wants to merge 1 commit into
NVIDIA:mainfrom
gabortar:fix-4876-output-fp16
Open

gabortar wants to merge 1 commit into
NVIDIA:mainfrom
gabortar:fix-4876-output-fp16

Conversation

@gabortar

Copy link
Copy Markdown

Fixes #4876.

Root cause

EmbLayerNormVarSeqlenPluginBase::getFieldsToSerialize() (used by CustomEmbLayerNormPluginDynamic v4 HFace and v5 MTron) pointed the output_fp16 PluginField at a local bool:

bool output_fp16 = mType == DataType::kHALF;
mDataToSerialize.emplace_back("output_fp16", &output_fp16, PluginFieldType::kINT32, 1);
  • Dangling pointer. TensorRT reads PluginField::data after getFieldsToSerialize() returns, by which time the local is gone.
  • Width mismatch. The field is declared kINT32 with length 1, so TensorRT copies 4 bytes from a 1-byte bool.

The engine therefore stores stack garbage for output_fp16. The creator decodes it with != 0, so an FP32 plugin is recreated as FP16 at deserialization. The first inference then aborts on PLUGIN_ASSERT(outputs[0].type == mType) (.cpp:354 for v5, :325 for v4). FP16 engines decode correctly only by accident.

Fix

  • Back the field with an int32_t mOutputFp16{} member, set in getFieldsToSerialize(). This is the same pattern embLayerNormPlugin.cpp and fftPlugin.cpp already use.
  • The serialized schema is unchanged: same field name, kINT32, length 1, same field order. Newly built engines carry exactly 0 or 1.
  • Only embLayerNormVarSeqlenPlugin.h and embLayerNormVarSeqlenPlugin.cpp change (+7/−4), with no build-system or API changes.

Validation

Linux, GCC 13.3, CUDA 13.0, TensorRT 11.4.0.106 (tensorrt-cu13 wheel), RTX 5070 Ti (SM 12.0).

The full repository CMake configure needs the TensorRT-Enterprise package, which I don't have. I therefore built the plugin sources (embLayerNormVarSeqlenPlugin*.cpp, the HFace/MTron kernels, plugin/common) into a standalone library against the 11.4 wheel. I validated two builds of that library with the same toolchain and flags: one from unmodified main (16ed063), one with this patch.

Regression test (RED/GREEN). A GoogleTest, run on the plugin and on its clone() for v4/v5 × FP32/FP16. For each case it:

  1. captures getFieldsToSerialize();
  2. overwrites the stack;
  3. checks that output_fp16 is a single kINT32 equal to exactly 0 or 1;
  4. recreates the plugin with TensorRTPhase::kRUNTIME;
  5. checks the output precision.
Without fix With fix
Plain, 5 runs 4/4 FAILED every run 4/4 PASSED every run
ASan stack-use-after-return on 'output_fp16' for each case 4/4 PASSED, 0 reports

ASan. The plugin and test were built with -fsanitize=address and run with detect_stack_use_after_return=1. Without the fix, ASan reports a 4-byte read from the 1-byte output_fp16 in getFieldsToSerialize()'s dead frame:

ERROR: AddressSanitizer: stack-use-after-return ... READ of size 4
    [48, 49) 'output_fp16' (line 286) <== Memory access at offset 48 partially overflows this variable

With the fix there are no ASan reports, in the unit test or in real engine build, deserialization and inference.

Real TensorRT 11.4 engines (build, then deserialize in a fresh process):

  • Runtime value. The output_fp16 value TensorRT passes to createPlugin(kRUNTIME) is exactly 00 00 00 00 / 01 00 00 00 for v4 and v5, FP32 and FP16. Without the fix it is garbage: c9 fd 7f 00 with the official 11.4 wheel, 00 04 00 00 with my unpatched source build. The issue's reproducer shows this directly with its inspect step.
  • Inference. MTron v5 FP32 and FP16 engines run and match a NumPy reference: max abs err 8.3e-7 for FP32, 6.0e-3 for FP16. Without the fix, FP32 aborts at :354.
  • HFace v4. The FP32 engine now gets past the :325 dtype assertion.

Additional regression coverage. The GoogleTest above and an engine-level Python test are not part of this PR, to keep it minimal. No plugin in the repo has gtests yet, and wiring one into plugin/ CMake needs a decision on your side. I'm happy to add them in this PR or a follow-up, structured however you prefer.

Notes

  • Older engines. FP32 v4/v5 engines serialized before this fix may contain a corrupted output_fp16 and need to be rebuilt.
  • Out of scope. HFace v4 inference on TensorRT 11.4 separately trips PLUGIN_ASSERT(outputs[1].dims.nbDims == 0) (.cpp:327), which this PR does not address.
  • Not tested on Windows/MSVC or QNX.

…inDynamic v4/v5

getFieldsToSerialize() pointed the kINT32 "output_fp16" PluginField at a
local bool. TensorRT reads PluginField::data after the call returns, so
it copied 4 bytes from a dead 1-byte stack slot, and FP32 engines could be
recreated as FP16 at deserialization.

Back the field with an int32_t member. The serialized schema is unchanged.

Signed-off-by: targabor <tar.gabor14@gmail.com>
@gabortar
gabortar requested a review from a team as a code owner October 13, 2026 20:13
@gabortar

Copy link
Copy Markdown
Author

Thanks for the approval, @pranavm-nvidia

Just checking whether there's anything else needed from my side before this can be merged.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

EmbLayerNormVarSeqlenPlugin: dangling pointer and out-of-bounds read during serialization

2 participants