Converting yolov7 .pt to .weights

alemelis: <p>Hello!<br>
I’ve already asked this question on yolov7 repository</p><aside class="onebox githubissue" data-onebox-src="https://github.com/WongKinYiu/yolov7/issues/947">
  <header class="source">

      <a href="https://github.com/WongKinYiu/yolov7/issues/947" target="_blank" rel="noopener nofollow ugc">github.com/WongKinYiu/yolov7</a>
  </header>

  <article class="onebox-body">
    <div class="github-row">
  <div class="github-icon-container" title="Issue">
	  <svg width="60" height="60" class="github-icon" viewbox="0 0 14 16" aria-hidden="true"><path fill-rule="evenodd" d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"></path></svg>
  </div>

  <div class="github-info-container">
    <h4>
      <a href="https://github.com/WongKinYiu/yolov7/issues/947" target="_blank" rel="noopener nofollow ugc">Translate .pt to .weights</a>
    </h4>

    <div class="github-info">
      <div class="date">
        opened <span class="discourse-local-date" data-format="ll" data-date="2022-10-15" data-time="07:37:57" data-timezone="UTC">07:37AM - 15 Oct 22 UTC</span>
      </div>


      <div class="user">
        <a href="https://github.com/alemelis" target="_blank" rel="noopener nofollow ugc">
          <img alt="alemelis" src="https://avatars.githubusercontent.com/u/4661737?v=4" class="onebox-avatar-inline" width="20" height="20">
          alemelis
        </a>
      </div>
    </div>

    <div class="labels">
    </div>
  </div>
</div>

  <div class="github-row">
    <p class="github-body-container">Hi @AlexeyAB @WongKinYiu 
I'm trying to translate my custom tinyv7 model from p<span class="show-more-container"><a href="" rel="noopener" class="show-more">…</a></span><span class="excerpt hidden">ytorch to darknet format (`.pt` -&gt; `.weights`)

I followed these steps https://github.com/AlexeyAB/darknet/issues/2587
but I don't get any detection. As a test I'm using your COCO yolov7-tiny.pt which should work just fine.

This is what I come up with so far

```python
import numpy as np
import torch

from models.yolo import Detect, Model


def load_cfg_pt(
    cfg_path: str,
    pt_path: str,
    fuse: bool = False,
) -&gt; tuple[dict, Model]:
    with open(cfg_path, "r") as f:
        cfg_lines = f.readlines()

    # skip batch norm layers
    if fuse:
        layers = [
            line.strip()
            for line in cfg_lines
            if line.strip() in ("[convolutional]", "[yolo]")
        ]
    else:
        layers = []
        for i, line in enumerate(cfg_lines):
            line = line.strip()
            if line == "[yolo]":
                layers.append(line)
            elif line == "[convolutional]":
                if cfg_lines[i + 1].strip() == "batch_normalize=1":
                    layers.append("[bn]")
                else:
                    layers.append(line)

    ckpt = torch.load(pt_path, map_location=torch.device('cpu'))
    model = ckpt.get('model').float()
    if fuse:
        model.fuse()
    model.eval()
    return layers, model


def numpize(t: torch.Tensor) -&gt; np.ndarray:
    return t.data.cpu().detach().numpy()


if __name__ == "__main__":
    cfg_path = "tinyv7.cfg"
    pt_path = "yolov7-tiny.pt"
    fuse = False

    if not fuse:
        print("Keep batch_norm layers!")

    arch, model = load_cfg_pt(cfg_path, pt_path, fuse)

    conv_b, conv_w = [], []
    bn_b, bn_w, bn_m, bn_v = [], [], [], []
    yolo_b, yolo_w = [], []
    for i, (_, module) in enumerate(model.named_modules()):
        # skip Model and Sequential
        if i &lt; 2:
            continue
        # parse layer-by-layer
        isconv = isinstance(module, torch.nn.modules.conv.Conv2d)
        isyolo = isinstance(module, Detect)
        isbn = isinstance(module, torch.nn.modules.batchnorm.BatchNorm2d)
        if not isconv and not isyolo and not isbn:
            continue

        if isyolo:
            for j, (_, yolo_layer) in enumerate(module.named_modules()):
                # skip Detect and ModuleList
                if j &lt; 2:
                    continue
                # parse layer-by-layer
                yolo_b.append(numpize(yolo_layer.bias))
                yolo_w.append(numpize(yolo_layer.weight))
        elif isconv:
            if isinstance(module.bias, torch.Tensor):
                conv_b.append(numpize(module.bias))
            conv_w.append(numpize(module.weight))
        else:  # isbn
            bn_b.append(numpize(module.bias))
            bn_w.append(numpize(module.weight))
            bn_m.append(numpize(module.running_mean))
            bn_v.append(numpize(module.running_var))

    # to fifo with pop()
    conv_w.reverse()
    conv_b.reverse()
    bn_w.reverse()
    bn_b.reverse()
    bn_m.reverse()
    bn_v.reverse()
    yolo_w.reverse()
    yolo_b.reverse()

    with open(f'darknet{"_nobn"*fuse}.weights', "wb") as f:
        # header
        np.array([0, 2, 5, 0, 0], dtype=np.int32).tofile(f)

        # https://github.com/AlexeyAB/darknet/issues/2587#issuecomment-500895636
        for v in arch:
            print(v)
            if v == "[yolo]":
                yolo_b.pop().tofile(f)
                yolo_w.pop().tofile(f)
            elif v == "[bn]":
                bn_b.pop().tofile(f)
                bn_w.pop().tofile(f)
                bn_m.pop().tofile(f)
                bn_v.pop().tofile(f)
                conv_w.pop().tofile(f)
            else:
                conv_b.pop().tofile(f)
                conv_w.pop().tofile(f)
```

1. is the header 5 or 20 bytes (int32) long? I tried both options with no luck
2. is pytorch tensor dimensions order the same of darknet? I tried some permutations, again no luck
3. this script outputs weights of 25.8MB while official darknet ones are smaller (25MB)
4. I tried with fusing enabled and disabled (changed `batch_normalize=0` in the cfg), still no luck.

can you spot any major issue with my approach?
thanks a lot! 🙏</span></p>
  </div>

  </article>

  <div class="onebox-metadata">
    
    
  </div>

  <div style="clear: both"></div>
</aside>
<p>
but I’m hoping to get more answers here <img src="https://discuss.pytorch.org/images/emoji/apple/stuck_out_tongue.png?v=12" title=":stuck_out_tongue:" class="emoji" alt=":stuck_out_tongue:" loading="lazy" width="20" height="20"></p>
<p>In short, I’m trying to convert a yolo model trained on pytorch to the original darknet format.<br>
Is anyone already attempted the conversion?<br>
My script is below</p>
<pre><code class="lang-python">import numpy as np
import torch

from models.yolo import Detect, Model


def load_cfg_pt(
    cfg_path: str,
    pt_path: str,
    fuse: bool = False,
) -&gt; tuple[dict, Model]:
    with open(cfg_path, "r") as f:
        cfg_lines = f.readlines()

    # skip batch norm layers
    if fuse:
        layers = [
            line.strip()
            for line in cfg_lines
            if line.strip() in ("[convolutional]", "[yolo]")
        ]
    else:
        layers = []
        for i, line in enumerate(cfg_lines):
            line = line.strip()
            if line == "[yolo]":
                layers.append(line)
            elif line == "[convolutional]":
                if cfg_lines[i + 1].strip() == "batch_normalize=1":
                    layers.append("[bn]")
                else:
                    layers.append(line)

    ckpt = torch.load(pt_path, map_location=torch.device('cpu'))
    model = ckpt.get('model').float()
    if fuse:
        model.fuse()
    model.eval()
    return layers, model


def numpize(t: torch.Tensor) -&gt; np.ndarray:
    return t.data.cpu().detach().numpy()


if __name__ == "__main__":
    cfg_path = "tinyv7.cfg"
    pt_path = "yolov7-tiny.pt"
    fuse = False

    if not fuse:
        print("Keep batch_norm layers!")

    arch, model = load_cfg_pt(cfg_path, pt_path, fuse)

    conv_b, conv_w = [], []
    bn_b, bn_w, bn_m, bn_v = [], [], [], []
    yolo_b, yolo_w = [], []
    for i, (_, module) in enumerate(model.named_modules()):
        # skip Model and Sequential
        if i &lt; 2:
            continue
        # parse layer-by-layer
        isconv = isinstance(module, torch.nn.modules.conv.Conv2d)
        isyolo = isinstance(module, Detect)
        isbn = isinstance(module, torch.nn.modules.batchnorm.BatchNorm2d)
        if not isconv and not isyolo and not isbn:
            continue

        if isyolo:
            for j, (_, yolo_layer) in enumerate(module.named_modules()):
                # skip Detect and ModuleList
                if j &lt; 2:
                    continue
                # parse layer-by-layer
                yolo_b.append(numpize(yolo_layer.bias))
                yolo_w.append(numpize(yolo_layer.weight))
        elif isconv:
            if isinstance(module.bias, torch.Tensor):
                conv_b.append(numpize(module.bias))
            conv_w.append(numpize(module.weight))
        else:  # isbn
            bn_b.append(numpize(module.bias))
            bn_w.append(numpize(module.weight))
            bn_m.append(numpize(module.running_mean))
            bn_v.append(numpize(module.running_var))

    # to fifo with pop()
    conv_w.reverse()
    conv_b.reverse()
    bn_w.reverse()
    bn_b.reverse()
    bn_m.reverse()
    bn_v.reverse()
    yolo_w.reverse()
    yolo_b.reverse()

    with open(f'darknet{"_nobn"*fuse}.weights', "wb") as f:
        # header
        np.array([0, 2, 5, 0, 0], dtype=np.int32).tofile(f)

        # https://github.com/AlexeyAB/darknet/issues/2587#issuecomment-500895636
        for v in arch:
            print(v)
            if v == "[yolo]":
                yolo_b.pop().tofile(f)
                yolo_w.pop().tofile(f)
            elif v == "[bn]":
                bn_b.pop().tofile(f)
                bn_w.pop().tofile(f)
                bn_m.pop().tofile(f)
                bn_v.pop().tofile(f)
                conv_w.pop().tofile(f)
            else:
                conv_b.pop().tofile(f)
                conv_w.pop().tofile(f)
</code></pre>
<p>this doesn’t give the right weights as the final binary size is larger than what it should be.</p>
<p>any idea?</p>