Skip to content

fix(pnnx): handle denormal float literals in pass_ncnn expression expansion#6748

Open
crystarm wants to merge 1 commit into
Tencent:masterfrom
crystarm:fix/pnnx-denorm-expression
Open

fix(pnnx): handle denormal float literals in pass_ncnn expression expansion#6748
crystarm wants to merge 1 commit into
Tencent:masterfrom
crystarm:fix/pnnx-denorm-expression

Conversation

@crystarm

Copy link
Copy Markdown

Related to #6614.

Summary

This PR fixes a pnnx conversion failure when pnnx.Expression contains denormal float literals (e.g. -4.928072e-40).

Root cause

tools/pnnx/src/pass_ncnn/expand_expression.cpp used std::stof to parse scalar literals.
For denormal/underflow values, std::stof may throw std::out_of_range on some platforms/libstdc++ implementations, which can interrupt pass_ncnn and lead to missing .ncnn.param/.bin outputs.

Changes

  • Replaced std::stof parsing in pass_ncnn/expand_expression.cpp with a strtof-based noexcept parser.
  • Accept underflow/subnormal values and reject true overflow only.
  • Added explicit error logging for invalid scalar literal parsing.
  • Added regression test:
    • tools/pnnx/tests/ncnn/test_ncnn_expression_denorm.py
  • Registered the test in:
    • tools/pnnx/tests/ncnn/CMakeLists.txt

Validation

  • python3 -m py_compile tools/pnnx/tests/ncnn/test_ncnn_expression_denorm.py

Compatibility

No API or model format changes.
Behavior for normal literals is unchanged; denormal literals no longer trigger this failure path.

@crystarm crystarm changed the title fix(pnnx): handle denormal float literals in pass_ncnn expression exp… fix(pnnx): handle denormal float literals in pass_ncnn expression expansion May 26, 2026
@nihui nihui requested a review from Copilot May 26, 2026 16:40

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses a pnnx -> ncnn conversion failure in pass_ncnn when pnnx.Expression contains denormal/subnormal float literals (e.g. -4.928072e-40) by replacing std::stof with a strtof-based parser and adding a regression test.

Changes:

  • Implemented a strtof-based float literal parser to accept underflow/subnormal values and reject true overflow.
  • Added explicit error logging when literal parsing fails during expression expansion.
  • Added and registered a regression test to ensure pass_ncnn produces outputs for expressions containing denormal literals.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
tools/pnnx/src/pass_ncnn/expand_expression.cpp Replaces std::stof scalar parsing with a strtof-based parser and adds error logging on parse failure.
tools/pnnx/tests/ncnn/test_ncnn_expression_denorm.py Adds a regression test that exports a TorchScript model containing denormal float literals and verifies pass_ncnn output + inference correctness.
tools/pnnx/tests/ncnn/CMakeLists.txt Registers the new denormal-expression regression test in the ncnn test suite.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +55 to +70
static bool parse_float_noexcept(const std::string& t, float& f)
{
errno = 0;

char* endptr = 0;
f = strtof(t.c_str(), &endptr);

if (endptr == t.c_str())
return false;

// strtof reports ERANGE for both overflow and underflow.
// Accept subnormal/underflow values and reject only true overflow.
if (errno == ERANGE && (f == HUGE_VALF || f == -HUGE_VALF))
return false;

return true;

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the catch. You're right that strtof accepts a valid prefix unless endptr is checked against the end of the string.

One note: this is not a new behavior introduced by this PR. The previous code used std::stof without checking pos, which also accepts a valid prefix and ignores trailing characters.

This PR is intentionally scoped to avoiding std::out_of_range on denormal/underflow literals in pass_ncnn. I agree that full-token validation is worth tightening, but I'd prefer to handle that separately to avoid mixing the denormal fix with a broader parser behavior change.

Comment on lines +30 to +52
# export torchscript
mod = torch.jit.trace(net, (x, y))
mod.save("test_ncnn_expression_denorm.pt")

# torchscript to pnnx
if (
os.system(
"../../src/pnnx test_ncnn_expression_denorm.pt inputshape=[1,4,8,8],[1,4,8,8]"
)
!= 0
):
return False

# ensure pass_ncnn produced output files
if not os.path.exists("test_ncnn_expression_denorm.ncnn.param"):
return False
if not os.path.exists("test_ncnn_expression_denorm.ncnn.bin"):
return False

# ensure the test model really contains denormalized literal expression
if not os.path.exists("test_ncnn_expression_denorm.pnnx.param"):
return False
with open("test_ncnn_expression_denorm.pnnx.param", "r", encoding="utf-8") as f:

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, thanks. Stale generated files can make this kind of test less robust.

For this PR I followed the existing tools/pnnx/tests/ncnn test style and kept the regression focused on the denormal-expression failure path. The test already checks the pnnx command result, verifies that the expected ncnn outputs are produced, confirms that the generated pnnx param still contains the denormal expression, and compares inference results.

I agree that cleanup/temp-directory isolation would make the tests more robust, but I think that is better handled as a broader test-suite cleanup rather than in this focused regression fix.

@crystarm

crystarm commented Jun 1, 2026

Copy link
Copy Markdown
Author

@nihui
Copilot comments are addressed in-thread. ᵔ ᵕ ᵔ

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants