Skip to content

Fix ConvTranspose#93

Merged
glenn-jocher merged 4 commits into
ultralytics:mainfrom
FrzMtrsprt:fix-convt
Aug 20, 2025
Merged

Fix ConvTranspose#93
glenn-jocher merged 4 commits into
ultralytics:mainfrom
FrzMtrsprt:fix-convt

Conversation

@FrzMtrsprt
Copy link
Copy Markdown

@FrzMtrsprt FrzMtrsprt commented Aug 19, 2025

Fixes #92

I have read the CLA Document and I sign the CLA

🛠️ PR Summary

Made with ❤️ by Ultralytics Actions

🌟 Summary

Improves THOP’s accuracy and clarity by correctly counting MACs for ConvTranspose layers, aligning terminology to MACs (not FLOPs), expanding tests, and releasing version 2.0.16. 🚀

📊 Key Changes

  • Corrected ConvTranspose operation counting:
    • Added count_convtNd hook and wired ConvTranspose1d/2d/3d to it.
    • Enhanced calculate_conv2d_flops with a transpose flag to compute MACs correctly for transposed convolutions.
  • Terminology alignment in tests: switched from “FLOPs” to “MACs” and updated assertions accordingly.
  • Added comprehensive ConvTranspose2d tests:
    • No-bias, with-bias, grouped, random configurations.
    • Symmetry test ensuring Conv2d downsample and ConvTranspose2d upsample have equal MACs in matching setups.
  • Minor test improvements: added readable debug prints for layer configs and shapes.
  • Version bump: 2.0.15 → 2.0.16.

🎯 Purpose & Impact

  • More accurate MAC reporting for ConvTranspose layers, fixing under/over-count issues users may have seen before. ✅
  • Clearer metrics: reinforces that THOP reports MACs, reducing confusion with “FLOPs.” 🧠
  • Stronger reliability via expanded test coverage, including grouped and symmetric cases. 🧪
  • Backward-compatible API; however, reported numbers for models using ConvTranspose may change to correct values. 📈

@UltralyticsAssistant UltralyticsAssistant added the enhancement New feature or request label Aug 19, 2025
@UltralyticsAssistant
Copy link
Copy Markdown
Member

👋 Hello @FrzMtrsprt, thank you for submitting an ultralytics/thop 🚀 PR! This is an automated response to help speed things along—an Ultralytics engineer will also review and assist you soon.

  • ✅ Define a Purpose: Clearly explain the purpose of your fix or feature in your PR description, and link to any relevant issues at https://github.com/ultralytics/thop/issues. Ensure your commit messages are clear, concise, and adhere to the project's conventions.
  • ✅ Synchronize with Source: Confirm your PR is synchronized with the ultralytics/thop main branch. If it's behind, update it by clicking the 'Update branch' button or by running git pull and git merge main locally.
  • ✅ Ensure CI Checks Pass: Verify all Ultralytics CI checks are passing at https://docs.ultralytics.com/help/CI/. If any checks fail, please address the issues.
  • ✅ Update Documentation: Update the relevant documentation at https://docs.ultralytics.com/ for any new or modified features.
  • ✅ Add Tests: If applicable, include or update tests to cover your changes, and confirm that all tests are passing.
  • ✅ Sign the CLA: Please ensure you have signed our Contributor License Agreement at https://docs.ultralytics.com/help/CLA/ if this is your first Ultralytics PR by writing "I have read the CLA Document and I sign the CLA" in a new message.
  • ✅ Minimize Changes: Limit your changes to the minimum necessary for your bug fix or feature addition. "It is not daily increase but daily decrease, hack away the unessential. The closer to the source, the less wastage there is." — Bruce Lee

If this PR addresses a bug, please include a Minimum Reproducible Example (MRE) if not already provided:

  • Python and library versions (Python, PyTorch, thop)
  • Minimal model snippet using ConvTranspose1d/2d/3d
  • The FLOPs/params output you get vs. what you expect
  • Exact commands used to run profiling

For more guidance, please refer to our Contributing Guide at https://docs.ultralytics.com/help/contributing/. Don’t hesitate to leave a comment if you have any questions. Thank you for contributing to Ultralytics! 🚀🛠️✨

Signed-off-by: Glenn Jocher <glenn.jocher@ultralytics.com>
@glenn-jocher glenn-jocher self-requested a review August 19, 2025 14:29
@glenn-jocher
Copy link
Copy Markdown
Member

@onuralpszr can you review the math here?

@onuralpszr
Copy link
Copy Markdown
Member

Of course, I am start reviewing

@onuralpszr can you review the math here?

…s, grouped, and random parameters.

Signed-off-by: Onuralp SEZER <onuralp@ultralytics.com>
@onuralpszr
Copy link
Copy Markdown
Member

onuralpszr commented Aug 20, 2025

First of all thank you for the PR @FrzMtrsprt first thing I notice and did some research to verify it.

fvcore return "MACs" despite they say "FLOPs" but their actual return is "MACs" author of fvcore people also accept and opened a PR long ago but still not merge yet to fix terminology.

URL1: Lyken17#208
URL2: facebookresearch/fvcore#69

For sanity check

import torch
from ultralytics import YOLO
from thop import clever_format, profile

model_names = ["yolo11n.pt", "yolo11s.pt", "yolo11m.pt", "yolo11l.pt", "yolo11x.pt"]
input_shape = [640, 640]
dummy_input = torch.randn(1, 3, input_shape[0], input_shape[1])

for model_name in model_names:
    model = YOLO(model_name)
    macs, params = profile(model.model, (dummy_input,), verbose=False)

    macs_readable, params_readable = clever_format([macs, params], "%.3f")
    flops_readable = clever_format([macs * 2, params], "%.3f")
    
    print(f"Total MACs for {model_name}: {macs_readable}")
    print(f"Total params for {model_name}: {params_readable}")
    print(f"Total FLOPs for {model_name}: {flops_readable}")
    print()
Total MACs for yolo11n.pt: 3.307G
Total params for yolo11n.pt: 2.624M
Total FLOPs for yolo11n.pt: ('6.614G', '2.624M')

Total MACs for yolo11s.pt: 10.859G
Total params for yolo11s.pt: 9.459M
Total FLOPs for yolo11s.pt: ('21.718G', '9.459M')

Total MACs for yolo11m.pt: 34.264G
Total params for yolo11m.pt: 20.115M
Total FLOPs for yolo11m.pt: ('68.528G', '20.115M')

Total MACs for yolo11l.pt: 43.807G
Total params for yolo11l.pt: 25.372M
Total FLOPs for yolo11l.pt: ('87.613G', '25.372M')

Total MACs for yolo11x.pt: 97.979G
Total params for yolo11x.pt: 56.966M
Total FLOPs for yolo11x.pt: ('195.959G', '56.966M')

For ConvTranspose I include tests and did tests to verify that also compare with fvcore(since it is also MACs)

New tests are

test_convtranspose2d_no_bias - ConvTranspose2d without bias
test_convtranspose2d - ConvTranspose2d with bias
test_convtranspose2d_groups - ConvTranspose2d with groups
test_convtranspose2d_random - Random ConvTranspose2d configurations
test_conv_vs_convtranspose_symmetry - Symmetry verification

All ConvTranspose2d tests use the correct formula: [input_size × (output_channels / groups) × kernel_size]
The symmetry test confirms Conv2d and ConvTranspose2d give equal MAC counts for symmetric operations
Random tests ensure the fix works across various configurations

For Extra Manuel Test to Verify the Math

import torch
import torch.nn as nn
from fvcore.nn import FlopCountAnalysis # despite it says "FlopCountAnalysis as I said it does return MACs
import thop


def verify_convtranspose_math():
    print("=" * 50)

    model = nn.ConvTranspose2d(3, 1, (2, 2), stride=(2, 2))
    input_tensor = torch.randn(1, 3, 2, 2)

    thop_result = thop.profile(model, inputs=(input_tensor,))
    thop_macs = thop_result[0]

    fvcore_flops = FlopCountAnalysis(model, (input_tensor,))
    fvcore_result = fvcore_flops.total()

    output = model(input_tensor)

    print(f"Input shape: {input_tensor.shape}")
    print(f"Output shape: {output.shape}")
    print(f"Kernel size: {model.kernel_size}")
    print(f"Stride: {model.stride}")
    print(f"Groups: {model.groups}")
    print()
    print("=" * 50)

    print("Manual MAC calculation for ConvTranspose2d")
    print("Formula: input_size × (output_channels / groups) × kernel_size")
    print()
    print("=" * 50)

    input_size = input_tensor.numel()
    output_channels = model.out_channels
    groups = model.groups
    kernel_h, kernel_w = model.kernel_size
    kernel_size = kernel_h * kernel_w

    manual_macs = input_size * (output_channels / groups) * kernel_size

    print(f"input_size = {input_tensor.shape} = {input_size} elements")
    print(f"output_channels / groups = {output_channels} / {groups} = {output_channels / groups}")
    print(f"kernel_size = {kernel_h} × {kernel_w} = {kernel_size}")
    print(f"MACs = {input_size} × {output_channels / groups} × {kernel_size} = {manual_macs}")
    print(f"FLOPs = 2 × MACs = 2 × {manual_macs} = {2 * manual_macs}")
    print()
    print("=" * 50)

    print("Results comparison:")
    print(f"thop MACs: {thop_macs}")
    print(f"fvcore MACs: {fvcore_result}")
    print(f"Manual MACs: {manual_macs}")
    print(f"Manual FLOPs: {2 * manual_macs}")
    print()

    return True


def demonstrate_conv_convtranspose_symmetry():
    print("\n3. Conv2d vs ConvTranspose2d symmetry:")
    print("-" * 42)

    # Create symmetric operations
    # Conv2d: downsample 4x4 -> 2x2
    conv = nn.Conv2d(1, 3, kernel_size=2, stride=2)
    conv_input = torch.randn(1, 1, 4, 4)

    # ConvTranspose2d: upsample 2x2 -> 4x4 (reverse)
    convt = nn.ConvTranspose2d(3, 1, kernel_size=2, stride=2)
    convt_input = torch.randn(1, 3, 2, 2)

    # Calculate MACs
    conv_result = thop.profile(conv, inputs=(conv_input,))
    conv_macs = conv_result[0]
    conv_output = conv(conv_input)

    convt_result = thop.profile(convt, inputs=(convt_input,))
    convt_macs = convt_result[0]
    convt_output = convt(convt_input)

    print(f"Conv2d: {conv_input.shape} -> {conv_output.shape}, MACs: {conv_macs}, FLOPs: {2 * conv_macs}")
    print(f"ConvTranspose2d: {convt_input.shape} -> {convt_output.shape}, MACs: {convt_macs}, FLOPs: {2 * convt_macs}")
    print()


def main():
    verify_convtranspose_math()
    demonstrate_conv_convtranspose_symmetry()


if __name__ == "__main__":
    exit(main())
==================================================
[INFO] Register count_convtNd() for <class 'torch.nn.modules.conv.ConvTranspose2d'>.
Input shape: torch.Size([1, 3, 2, 2])
Output shape: torch.Size([1, 1, 4, 4])
Kernel size: (2, 2)
Stride: (2, 2)
Groups: 1

==================================================
Manual MAC calculation for ConvTranspose2d
Formula: input_size × (output_channels / groups) × kernel_size

==================================================
input_size = torch.Size([1, 3, 2, 2]) = 12 elements
output_channels / groups = 1 / 1 = 1.0
kernel_size = 2 × 2 = 4
MACs = 12 × 1.0 × 4 = 48.0
FLOPs = 2 × MACs = 2 × 48.0 = 96.0

==================================================
Results comparison:
thop MACs: 48.0
fvcore MACs: 48
Manual MACs: 48.0
Manual FLOPs: 96.0


3. Conv2d vs ConvTranspose2d symmetry:
------------------------------------------
[INFO] Register count_convNd() for <class 'torch.nn.modules.conv.Conv2d'>.
[INFO] Register count_convtNd() for <class 'torch.nn.modules.conv.ConvTranspose2d'>.
Conv2d: torch.Size([1, 1, 4, 4]) -> torch.Size([1, 3, 2, 2]), MACs: 48.0, FLOPs: 96.0
ConvTranspose2d: torch.Size([1, 3, 2, 2]) -> torch.Size([1, 1, 4, 4]), MACs: 48.0, FLOPs: 96.0

As an extra I fixed all of the terms in conv2d test FLOPs to MACs to reflect correct terms as well.

Thank you again for PR 👍

@onuralpszr
Copy link
Copy Markdown
Member

cc @glenn-jocher I wrote a detailed analysis and checked and include tests make sure we cover new parameter.

@glenn-jocher
Copy link
Copy Markdown
Member

@onuralpszr awesome analysis, thanks for the detailed comparison here. Agree sometimes the two terms are used interchangeably even though they're not the same.

@FrzMtrsprt merging PR and releasing a new version of the package with your fix. Thank you for your contributions!

@glenn-jocher glenn-jocher merged commit 883c628 into ultralytics:main Aug 20, 2025
3 checks passed
@UltralyticsAssistant
Copy link
Copy Markdown
Member

Merged and marvelous! 🎉 Huge thanks to @FrzMtrsprt for leading this, with thoughtful contributions from @glenn-jocher and @onuralpszr. As Peter Drucker said, “What gets measured gets managed.” This PR makes THOP’s measurements more trustworthy by correctly counting MACs for ConvTranspose layers, aligning terminology from FLOPs to MACs, and strengthening tests—culminating in v2.0.16. The result: clearer, accurate metrics and more reliable benchmarking for our community and tooling. Grateful for the precision and care you brought to this—awesome work, team! 🚀

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

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Wrong calculation for ConvTranspose

4 participants