Repository: tenstorrent/tt-metal
Pull request #18937: #17700: Yolov9c model ttnn bringup
Author: login: kkeerthana0573
Created: 2025-03-11T12:13:06Z | Merged: 2025-03-25T18:50:47Z

### Ticket
[Link to Github Issue](https://github.com/tenstorrent/tt-metal/issues/17700)

### Problem description
- Add optimized ttnn support for the Yolov9c model.

### What's changed
- Added Reference pipeline for Yolov9c.
- Added ttnn support for the model.

### Checklist
- [x] [All post commit](https://github.com/tenstorrent/tt-metal/actions/workflows/all-post-commit-workflows.yaml) CI passes
- [ ] [Blackhole Post commit](https://github.com/tenstorrent/tt-metal/actions/workflows/blackhole-post-commit.yaml) CI passes (if applicable)
- [ ] [Model regression](https://github.com/tenstorrent/tt-metal/actions/workflows/perf-models.yaml) CI passes (if applicable)
- [ ] [Device performance regression](https://github.com/tenstorrent/tt-metal/actions/workflows/perf-device-models.yaml) CI passes (if applicable)
- [ ] **(For models and ops writers)** Full [new models tests](https://github.com/tenstorrent/tt-metal/actions/workflows/full-new-models-suite.yaml) CI passes (if applicable)
- [ ] New/Existing tests provide coverage for changes


Diff:
diff --git a/models/experimental/functional_yolov9c/README.md b/models/experimental/functional_yolov9c/README.md
new file mode 100644
index 000000000000..ca713e2ce4e4
--- /dev/null
+++ b/models/experimental/functional_yolov9c/README.md
@@ -0,0 +1,21 @@
+# Yolov9c Demo
+Demo showcasing Yolov9c running on Wormhole - n150, n300 using ttnn.
+
+## Platforms:
+    WH N150, N300
+
+## Introduction:
+Yolov9 marks a significant advancement in real-time object detection, introducing groundbreaking techniques such as Programmable Gradient Information (PGI) and the Generalized Efficient Layer Aggregation Network (GELAN). Yolov9c is a compact and optimized variant of Yolov9, designed for efficient object detection with reduced computational overhead. It balances speed and accuracy.
+
+## Details:
+The entry point to functional_yolov9c model is YoloV9 in `models/experimental/functional_yolov9c/tt/ttnn_yolov9c.py`. The model picks up certain configs and weights from Ultralytics pretrained model. We've used weights available [here](https://docs.ultralytics.com/models/yolov9/#performance-on-ms-coco-dataset) under YOLOv9c.
+
+
+## How to Run:
+Use the following command to run the Yolov9c model :
+```
+pytest --disable-warnings tests/ttnn/integration_tests/yolov9c/test_ttnn_yolov9c.py::test_yolov9c
+```
+
+
+Torch ops: avg_pool2d
diff --git a/models/experimental/functional_yolov9c/demo/demo_utils.py b/models/experimental/functional_yolov9c/demo/demo_utils.py
new file mode 100644
index 000000000000..8acb6f7cd4f3
--- /dev/null
+++ b/models/experimental/functional_yolov9c/demo/demo_utils.py
@@ -0,0 +1,105 @@
+# SPDX-FileCopyrightText: © 2025 Tenstorrent Inc.
+
+# SPDX-License-Identifier: Apache-2.0
+
+import os
+import cv2
+import math
+import time
+import torch
+import requests
+import torchvision
+import numpy as np
+import torch.nn as nn
+from pathlib import Path
+from loguru import logger
+from datetime import datetime
+from functools import partial
+
+
+def imread(filename: str, flags: int = cv2.IMREAD_COLOR):
+    return cv2.imdecode(np.fromfile(filename, np.uint8), flags)
+
+
+class Ensemble(nn.ModuleList):
+    def __init__(self):
+        super(Ensemble, self).__init__()
+
+    def forward(self, x, augment=False):
+        y = []
+        for module in self:
+            y.append(module(x, augment)[0])
+        y = torch.cat(y, 1)
+        return y, None
+
+
+def attempt_download(file, repo="ultralytics/assets"):
+    tests = Path(__file__).parent.parent / "reference"
+    file_path = tests / Path(str(file).strip().replace("'", "").lower())
+
+    if not file_path.exists():
+        name = "yolov9c.pt"  # file_path.name
+        msg = f"{file_path} missing, try downloading from https://github.com/{repo}/releases/"
+
+        try:
+            url = f"https://github.com/{repo}/releases/download/v8.3.0/{name}"
+
+            logger.info(f"Downloading {url} to {file_path}...")
+            torch.hub.download_url_to_file(url, file_path)
+
+            # Validate the file
+            assert file_path.exists() and file_path.stat().st_size > 1e6, f"Download failed for {name}"
+
+        except Exception as e:
+            logger.warning(f"Error downloading from GitHub: {e}. Trying secondary source...")
+
+            url = f"https://storage.googleapis.com/{repo}/ckpt/{name}"
+            logger.info(f"Downloading {url} to {file_path}...")
+            os.system(f"curl -L {url} -o {file_path}")
+
+            if not file_path.exists() or file_path.stat().st_size < 1e6:
+                file_path.unlink(missing_ok=True)
+                logger.error(f"ERROR: Download failure for {msg}")
+            else:
+                logger.info(f"Download succeeded from secondary source!")
+    return file_path
+
+
+# Function to load weights into the model
+def attempt_load(weights, map_location=None):
+    model = Ensemble()
+
+    # Iterate through the weights and load them
+    for w in weights if isinstance(weights, list) else [weights]:
+        weight_path = attempt_download(w)
+        ckpt = torch.load(weight_path, map_location=map_location)
+        model.append(ckpt["ema" if ckpt.get("ema") else "model"].float().eval())
+    for m in model.modules():
+        if isinstance(m, (nn.Hardswish, nn.LeakyReLU, nn.ReLU, nn.ReLU6, nn.SiLU)):
+            m.inplace = True
+        elif isinstance(m, nn.Upsample):
+            m.recompute_scale_factor = None
+
+    if len(model) == 1:
+        return model[-1]
+    else:
+        for k in ["names", "stride"]:
+            setattr(model, k, getattr(model[-1], k))
+        return model
+
+
+def load_coco_class_names():
+    url = "https://raw.githubusercontent.com/pjreddie/darknet/master/data/coco.names"
+    path = f"models/demos/yolov4/demo/coco.names"
+    response = requests.get(url)
+    try:
+        response = requests.get(url, timeout=5)
+        if response.status_code == 200:
+            return response.text.strip().split("\n")
+    except requests.RequestException:
+        pass
+    if os.path.exists(path):
+        with open(path, "r") as f:
+            return [line.strip() for line in f.readlines()]
+
+    raise Exception("Failed to fetch COCO class names from both online and local sources.")
diff --git a/models/experimental/functional_yolov9c/reference/yolov9c.py b/models/experimental/functional_yolov9c/reference/yolov9c.py
new file mode 100644
index 000000000000..5bc381d440d7
--- /dev/null
+++ b/models/experimental/functional_yolov9c/reference/yolov9c.py
@@ -0,0 +1,420 @@
+# SPDX-FileCopyrightText: © 2025 Tenstorrent Inc.
+
+# SPDX-License-Identifier: Apache-2.0
+
+import torch
+import torch.nn as nn
+import torch.nn.functional as f
+import math
+
+
+def make_anchors(feats, strides, grid_cell_offset=0.5):
+    anchor_points, stride_tensor = [], []
+    assert feats is not None
+    dtype, device = feats[0].dtype, feats[0].device
+    for i, stride in enumerate(strides):
+        _, _, h, w = feats[i].shape
+        sx = torch.arange(end=w, device=device, dtype=dtype) + grid_cell_offset  # shift x
+        sy = torch.arange(end=h, device=device, dtype=dtype) + grid_cell_offset  # shift y
+        sy, sx = torch.meshgrid(sy, sx)
+        anchor_points.append(torch.stack((sx, sy), -1).view(-1, 2))
+        stride_tensor.append(torch.full((h * w, 1), stride, dtype=dtype, device=device))
+    return torch.cat(anchor_points), torch.cat(stride_tensor)
+
+
+class Conv(nn.Module):
+    def __init__(
+        self, in_channel, out_channel, kernel=1, stride=1, padding=0, dilation=1, groups=1, enable_identity=False
+    ):
+        super().__init__()
+        self.conv = nn.Conv2d(
+            in_channel,
+            out_channel,
+            kernel,
+            stride=stride,
+            padding=padding,
+            dilation=dilation,
+            groups=groups,
+            bias=False,
+        )
+        self.bn = nn.BatchNorm2d(out_channel, eps=0.001, momentum=0.03)
+        if enable_identity:
+            self.act = nn.Identity()
+        else:
+            self.act = nn.SiLU(inplace=True)
+
+    def forward(self, x):
+        x = self.conv(x)
+        x = self.bn(x)
+        x = self.act(x)
+        return x
+
+
+class RepConv(nn.Module):
+    default_act = nn.SiLU()
+
+    def __init__(self, in_channel, out_channel, k=3, s=1, p=1, g=1, d=1):
+        super().__init__()
+        self.act = nn.SiLU(inplace=True)
+        self.conv1 = Conv(in_channel, out_channel, 3, 1, padding=1, groups=1, enable_identity=True)
+        self.conv2 = Conv(in_channel, out_channel, 1, 1, padding=0, groups=1, enable_identity=True)
+
+    def forward(self, x):
+        return self.act(self.conv1(x) + self.conv2(x))
+
+
+class Bottleneck(nn.Module):
+    def __init__(self, in_channel, out_channel):
+        super().__init__()
+        self.cv1 = Conv(in_channel, out_channel)
+        self.cv2 = Conv(in_channel, out_channel, 3, padding=1)
+
+    def forward(self, x):
+        torch_input = x
+        return torch_input + self.cv2(self.cv1(x))
+
+
+class RepBottleneck(Bottleneck):
+    def __init__(self, in_channel, out_channel):
+        super().__init__(in_channel // 2, out_channel // 2)
+        self.cv1 = RepConv(in_channel // 2, out_channel // 2)
+
+
+class C3(nn.Module):
+    def __init__(self, in_channel, out_channel, n=1, shortcut=True, g=1, e=0.5):
+        super().__init__()
+        self.cv1 = Conv(in_channel // 2, out_channel // 4, 1, 1)
+        self.cv2 = Conv(in_channel // 2, out_channel // 4, 1, 1)
+        self.cv3 = Conv(in_channel // 2, out_channel // 2)
+
+    def forward(self, x):
+        return self.cv3(torch.cat((self.m(self.cv1(x)), self.cv2(x)), 1))
+
+
+class RepCSP(C3):
+    def __init__(self, in_channel, out_channel):
+        super().__init__(in_channel, out_channel)
+        self.m = nn.Sequential(*(RepBottleneck(in_channel // 2, out_channel // 2) for _ in range(1)))
+
+
+class RepNCSPELAN4(nn.Module):
+    def __init__(self, input_channel, output_channel, cv2_inc, cv2_outc, cv3_inc, cv3_outc, cv4_inc, cv4_out_c, n=1):
+        super().__init__()
+        self.cv1 = Conv(input_channel, output_channel)
+        self.cv2 = nn.Sequential(RepCSP(output_channel, output_channel), Conv(cv2_inc, cv2_outc, 3, 1, padding=1))
+        self.cv3 = nn.Sequential(RepCSP(output_channel, output_channel), Conv(cv3_inc, cv3_outc, 3, 1, padding=1))
+        self.cv4 = Conv(cv4_inc, cv4_out_c, 1, 1)
+
+    def forward(self, x):
+        y = list(self.cv1(x).chunk(2, 1))
+        y.extend((m(y[-1])) for m in [self.cv2, self.cv3])
+        return self.cv4(torch.cat(y, 1))
+
+
+class ADown(nn.Module):
+    def __init__(self, in_channel, out_channel):
+        super().__init__()
+        self.cv1 = Conv(in_channel, out_channel, 3, 2, 1)
+        self.cv2 = Conv(in_channel, out_channel, 1, 1, 0)
+
+    def forward(self, x):
+        x = torch.nn.functional.avg_pool2d(x, 2, 1, 0, False, True)
+        x1, x2 = x.chunk(2, 1)
+        x1 = self.cv1(x1)
+        x2 = torch.nn.functional.max_pool2d(x2, 3, 2, 1)
+        x2 = self.cv2(x2)
+        return torch.cat((x1, x2), 1)
+
+
+class SPPELAN(nn.Module):
+    def __init__(self, input_channel, output_channel, k=5):
+        super().__init__()
+        self.cv1 = Conv(input_channel, output_channel, 1, 1)
+        self.cv2 = nn.MaxPool2d(kernel_size=k, stride=1, padding=k // 2)
+        self.cv3 = nn.MaxPool2d(kernel_size=k, stride=1, padding=k // 2)
+        self.cv4 = nn.MaxPool2d(kernel_size=k, stride=1, padding=k // 2)
+        self.cv5 = Conv(input_channel * 2, input_channel)
+
+    def forward(self, x):
+        y = [self.cv1(x)]
+        y.extend(m(y[-1]) for m in [self.cv2, self.cv3, self.cv4])
+        return self.cv5(torch.cat(y, 1))
+
+
+class DFL(nn.Module):
+    def __init__(self):
+        super(DFL, self).__init__()
+        self.conv = nn.Conv2d(16, 1, kernel_size=1, stride=1, bias=False)
+
+    def forward(self, x):
+        return self.conv(x)
+
+
+class Detect(nn.Module):
+    def __init__(self, in_channel):
+        super().__init__()
+        self.in_channel = in_channel
+        self.cv2 = nn.ModuleList(
+            [
+                nn.Sequential(
+                    Conv(
+                        in_channel[0],
+                        64,
+                        3,
+                        padding=1,
+                    ),
+                    Conv(
+                        in_channel[1],
+                        64,
+                        3,
+                        padding=1,
+                    ),
+                    nn.Conv2d(
+                        in_channel[2],
+                        64,
+                        1,
+                    ),
+                ),
+                nn.Sequential(
+                    Conv(
+                        in_channel[3],
+                        64,
+                        3,
+                        padding=1,
+                    ),
+                    Conv(
+                        in_channel[4],
+                        64,
+                        3,
+                        padding=1,
+                    ),
+                    nn.Conv2d(
+                        in_channel[5],
+                        64,
+                        1,
+                    ),
+                ),
+                nn.Sequential(
+                    Conv(
+                        in_channel[6],
+                        64,
+                        3,
+                        padding=1,
+                    ),
+                    Conv(
+                        in_channel[7],
+                        64,
+                        3,
+                        padding=1,
+                    ),
+                    nn.Conv2d(
+                        in_channel[8],
+                        64,
+                        1,
+                    ),
+                ),
+            ]
+        )
+        self.cv3 = nn.ModuleList(
+            [
+                nn.Sequential(
+                    Conv(
+                        in_channel[9],
+                        256,
+                        3,
+                        padding=1,
+                    ),
+                    Conv(
+                        in_channel[10],
+                        256,
+                        3,
+                        padding=1,
+                    ),
+                    nn.Conv2d(
+                        in_channel[11],
+                        80,
+                        1,
+                    ),
+                ),
+                nn.Sequential(
+                    Conv(
+                        in_channel[12],
+                        256,
+                        3,
+                        padding=1,
+                    ),
+                    Conv(
+                        in_channel[13],
+                        256,
+                        3,
+                        padding=1,
+                    ),
+                    nn.Conv2d(
+                        in_channel[14],
+                        80,
+                        1,
+                    ),
+                ),
+                nn.Sequential(
+                    Conv(
+                        in_channel[15],
+                        256,
+                        3,
+                        padding=1,
+                    ),
+                    Conv(
+                        in_channel[16],
+                        256,
+                        3,
+                        padding=1,
+                    ),
+                    nn.Conv2d(
+                        in_channel[17],
+                        80,
+                        1,
+                    ),
+                ),
+            ]
+        )
+        self.dfl = DFL()
+
+    def forward(self, y1, y2, y3):
+        x1 = self.cv2[0](y1)
+        x2 = self.cv2[1](y2)
+        x3 = self.cv2[2](y3)
+        x4 = self.cv3[0](y1)
+        x5 = self.cv3[1](y2)
+        x6 = self.cv3[2](y3)
+
+        y1 = torch.cat((x1, x4), 1)
+        y2 = torch.cat((x2, x5), 1)
+        y3 = torch.cat((x3, x6), 1)
+        y_all = [y1, y2, y3]
+
+        y1 = torch.reshape(y1, (y1.shape[0], y1.shape[1], y1.shape[2] * y1.shape[3]))
+        y2 = torch.reshape(y2, (y2.shape[0], y2.shape[1], y2.shape[2] * y2.shape[3]))
+        y3 = torch.reshape(y3, (y3.shape[0], y3.shape[1], y3.shape[2] * y3.shape[3]))
+
+        y = torch.cat((y1, y2, y3), 2)
+        ya, yb = y.split((64, 80), 1)
+
+        ya = torch.reshape(ya, (ya.shape[0], int(ya.shape[1] / 16), 16, ya.shape[2]))
+        ya = torch.permute(ya, (0, 2, 1, 3))
+        ya = f.softmax(ya, dim=1)
+        c = self.dfl(ya)
+        c1 = torch.reshape(c, (c.shape[0], c.shape[1] * c.shape[2], c.shape[3]))
+        c2 = c1
+        c1 = c1[:, 0:2, :]
+        c2 = c2[:, 2:4, :]
+
+        anchor, strides = (y_all.transpose(0, 1) for y_all in make_anchors(y_all, [8, 16, 32], 0.5))
+        anchor.unsqueeze(0)
+
+        c1 = anchor - c1
+        c2 = anchor + c2
+
+        z1 = c2 - c1
+        z2 = c1 + c2
+        z2 = z2 / 2
+
+        z = torch.concat((z2, z1), 1)
+        z = z * strides
+        yb = torch.sigmoid(yb)
+        out = torch.concat((z, yb), 1)
+        return out
+
+
+class YoloV9(nn.Module):
+    def __init__(self):
+        super().__init__()
+        self.model = nn.Sequential(
+            Conv(3, 64, kernel=3, stride=2, padding=1),  # 0
+            Conv(64, 128, kernel=3, stride=2, padding=1),  # 1
+            RepNCSPELAN4(128, 128, 64, 64, 64, 64, 256, 256),  # 2
+            ADown(128, 128),  # 3
+            RepNCSPELAN4(256, 256, 128, 128, 128, 128, 512, 512),  # 4
+            ADown(256, 256),  # 5
+            RepNCSPELAN4(512, 512, 256, 256, 256, 256, 1024, 512),  # 6
+            ADown(256, 256),  # 7
+            RepNCSPELAN4(512, 512, 256, 256, 256, 256, 1024, 512),  # 8
+            SPPELAN(512, 256),  # 9
+            nn.Upsample(scale_factor=2.0, mode="nearest"),  # 10
+            Concat(),  # 11
+            RepNCSPELAN4(1024, 512, 256, 256, 256, 256, 1024, 512),  # 12
+            nn.Upsample(scale_factor=2.0, mode="nearest"),  # 13
+            Concat(),  # 14
+            RepNCSPELAN4(1024, 256, 128, 128, 128, 128, 512, 256),  # 15
+            ADown(128, 128),  # 16
+            Concat(),  # 17
+            RepNCSPELAN4(768, 512, 256, 256, 256, 256, 1024, 512),  # 18
+            ADown(256, 256),  # 19
+            Concat(),  # 20
+            RepNCSPELAN4(1024, 512, 256, 256, 256, 256, 1024, 512),  # 21
+            Detect([256, 64, 64, 512, 64, 64, 512, 64, 64, 256, 256, 256, 512, 256, 256, 512, 256, 256]),  # 22
+        )
+
+    def forward(self, x):
+        x = self.model[0](x)  # 0
+        x = self.model[1](x)  # 1
+        x = self.model[2](x)  # 2
+        x = self.model[3](x)  # 3
+        x = self.model[4](x)  # 4
+        x4 = x
+        x = self.model[5](x)  # 5
+        x = self.model[6](x)  # 6
+        x6 = x
+        x = self.model[7](x)  # 7
+        x = self.model[8](x)  # 8
+        x = self.model[9](x)  # 9
+        x10 = x
+        x = f.upsample(x, scale_factor=2.0)  # 10
+        x = torch.cat((x, x6), 1)  # 11
+        x = self.model[12](x)  # 12
+        x13 = x
+        x = f.upsample(x, scale_factor=2.0)  # 13
+        x = torch.cat((x, x4), 1)  # 14
+        x = self.model[15](x)  # 15
+        x16 = x
+        x = self.model[16](x)  # 16
+        x = torch.cat((x, x13), 1)  # 17
+        x = self.model[18](x)  # 18
+        x19 = x
+        x = self.model[19](x)  # 19
+        x = torch.cat((x, x10), 1)  # 20
+        x = self.model[21](x)  # 21
+        x22 = x
+        x = self.model[22](y1=x16, y2=x19, y3=x22)  # 22
+        return x
+
+
+class Concat(nn.Module):
+    def __init__(self, dimension=1):
+        super().__init__()
+        self.d = dimension
+
+    def forward(self, x):
+        return torch.cat(x, self.d)
+
+
+class BaseModel(nn.Module):
+    def forward(self, x, *args, **kwargs):
+        if isinstance(x, dict):
+            return self.loss(x, *args, **kwargs)
+        return self.predict(x, *args, **kwargs)
+
+    def predict(self, x, profile=False, visualize=False, augment=False, embed=None):
+        return self._predict_once(x, profile, visualize, embed)
+
+    def _predict_once(self, x, profile=False, visualize=False, embed=None):
+        y, dt, embeddings = [], [], []
+        for m in self.model:
+            if m.f != -1:
+                x = y[m.f] if isinstance(m.f, int) else [x if j == -1 else y[j] for j in m.f]
+            x = m(x)
+            y.append(x if m.i in self.save else None)
+        return x
+
+
+class DetectionModel(BaseModel):
+    def __init__(self, cfg="yolov9c.yaml", ch=3, nc=None, verbose=True):
+        super().__init__()
diff --git a/models/experimental/functional_yolov9c/tt/model_preprocessing.py b/models/experimental/functional_yolov9c/tt/model_preprocessing.py
new file mode 100644
index 000000000000..0030ea1f1002
--- /dev/null
+++ b/models/experimental/functional_yolov9c/tt/model_preprocessing.py
@@ -0,0 +1,123 @@
+# SPDX-FileCopyrightText: © 2025 Tenstorrent Inc.
+
+# SPDX-License-Identifier: Apache-2.0
+
+import ttnn
+import torch
+import torch.nn as nn
+import torch.nn.functional as F
+from ttnn.model_preprocessing import infer_ttnn_module_args
+from models.experimental.functional_yolov9c.reference.yolov9c import Conv, YoloV9
+from ttnn.model_preprocessing import preprocess_model_parameters, fold_batch_norm2d_into_conv2d
+
+
+def custom_preprocessor(model, name):
+    parameters = {}
+    if isinstance(model, nn.Conv2d):
+        parameters["weight"] = ttnn.from_torch(model.weight, dtype=ttnn.float32)
+        if model.bias is not None:
+            bias = model.bias.reshape((1, 1, 1, -1))
+            parameters["bias"] = ttnn.from_torch(bias, dtype=ttnn.float32)
+
+    if isinstance(model, Conv):
+        weight, bias = fold_batch_norm2d_into_conv2d(model.conv, model.bn)
+        parameters["conv"] = {}
+        parameters["conv"]["weight"] = ttnn.from_torch(weight, dtype=ttnn.float32)
+        bias = bias.reshape((1, 1, 1, -1))
+        parameters["conv"]["bias"] = ttnn.from_torch(bias, dtype=ttnn.float32)
+
+    return parameters
+
+
+def create_yolov9c_input_tensors(
+    device, batch_size=1, input_channels=3, input_height=640, input_width=640, model=False
+):
+    torch_input_tensor = torch.randn(batch_size, input_channels, input_height, input_width)
+    ttnn_input_tensor = torch.permute(torch_input_tensor, (0, 2, 3, 1))
+    memory_config = None
+    if model:
+        ttnn_input_tensor = F.pad(ttnn_input_tensor, (0, 29))
+        memory_config = ttnn.create_sharded_memory_config(
+            [6400, 32],
+            core_grid=device.core_grid,
+            strategy=ttnn.ShardStrategy.HEIGHT,
+            orientation=ttnn.ShardOrientation.ROW_MAJOR,
+            use_height_and_width_as_shard_shape=True,
+        )
+    memory_config = memory_config if memory_config else ttnn.L1_MEMORY_CONFIG
+    ttnn_input_tensor = ttnn.from_torch(
+        ttnn_input_tensor, dtype=ttnn.bfloat8_b, device=device, layout=ttnn.TILE_LAYOUT, memory_config=memory_config
+    )
+    return torch_input_tensor, ttnn_input_tensor
+
+
+def make_anchors(device, feats, strides, grid_cell_offset=0.5):
+    anchor_points, stride_tensor = [], []
+    assert feats is not None
+    for i, stride in enumerate(strides):
+        h, w = feats[i], feats[i]
+        sx = torch.arange(end=w) + grid_cell_offset
+        sy = torch.arange(end=h) + grid_cell_offset
+        sy, sx = torch.meshgrid(sy, sx)
+        anchor_points.append(torch.stack((sx, sy), -1).view(-1, 2))
+        stride_tensor.append(torch.full((h * w, 1), stride))
+
+    a = torch.cat(anchor_points).transpose(0, 1).unsqueeze(0)
+    b = torch.cat(stride_tensor).transpose(0, 1)
+
+    return (
+        ttnn.from_torch(a, dtype=ttnn.bfloat16, layout=ttnn.TILE_LAYOUT, device=device),
+        ttnn.from_torch(b, dtype=ttnn.bfloat16, layout=ttnn.TILE_LAYOUT, device=device),
+    )
+
+
+def create_yolov9c_model_parameters(model: YoloV9, input_tensor: torch.Tensor, device):
+    parameters = preprocess_model_parameters(
+        initialize_model=lambda: model,
+        custom_preprocessor=custom_preprocessor,
+        device=device,
+    )
+    parameters.conv_args = {}
+    parameters.conv_args = infer_ttnn_module_args(model=model, run_model=lambda model: model(input_tensor), device=None)
+
+    parameters["model_args"] = model
+
+    feats = [
+        input_tensor.shape[3] // 8,
+        input_tensor.shape[3] // 16,
+        input_tensor.shape[3] // 32,
+    ]
+    strides = [8.0, 16.0, 32.0]
+
+    anchors, strides = make_anchors(device, feats, strides)
+    if "model" in parameters:
+        parameters.model[22]["anchors"] = anchors
+        parameters.model[22]["strides"] = strides
+
+    return parameters
+
+
+def create_yolov9c_model_parameters_detect(
+    model: YoloV9, input_tensor_1: torch.Tensor, input_tensor_2: torch.Tensor, input_tensor_3: torch.Tensor, device
+):
+    parameters = preprocess_model_parameters(
+        initialize_model=lambda: model,
+        custom_preprocessor=custom_preprocessor,
+        device=device,
+    )
+    parameters.conv_args = {}
+    parameters.conv_args = infer_ttnn_module_args(
+        model=model, run_model=lambda model: model(input_tensor_1, input_tensor_2, input_tensor_3), device=None
+    )
+    parameters["model_args"] = model
+
+    feats = [80, 40, 20]  # Values depends on input resolution. Current: 640x640
+    strides = [8.0, 16.0, 32.0]
+
+    anchors, strides = make_anchors(device, feats, strides)  # Optimization: Processing make anchors outside model run
+
+    parameters["anchors"] = anchors
+    parameters["strides"] = strides
+    parameters["model"] = model
+
+    return parameters
diff --git a/models/experimental/functional_yolov9c/tt/ttnn_yolov9c.py b/models/experimental/functional_yolov9c/tt/ttnn_yolov9c.py
new file mode 100644
index 000000000000..25bb4ca44947
--- /dev/null
+++ b/models/experimental/functional_yolov9c/tt/ttnn_yolov9c.py
@@ -0,0 +1,630 @@
+# SPDX-FileCopyrightText: © 2025 Tenstorrent Inc.
+
+# SPDX-License-Identifier: Apache-2.0
+
+import math
+import ttnn
+import torch
+from tt_lib.utils import _nearest_y
+from models.experimental.yolo_common.yolo_utils import concat, determine_num_cores, get_core_grid_from_num_cores
+
+
+def interleaved_to_sharded(x):
+    x = ttnn.to_layout(x, layout=ttnn.ROW_MAJOR_LAYOUT)
+    nhw = x.shape[0] * x.shape[1] * x.shape[2]
+    num_cores = determine_num_cores(nhw, int(math.sqrt(x.shape[2])))
+    num_cores = 56 if num_cores == 1 else num_cores
+    core_grid = get_core_grid_from_num_cores(num_cores)
+    shardspec = ttnn.create_sharded_memory_config_(
+        x.shape, core_grid, ttnn.ShardStrategy.HEIGHT, orientation=ttnn.ShardOrientation.ROW_MAJOR
+    )
+
+    return ttnn.reshard(x, shardspec) if x.is_sharded() else ttnn.interleaved_to_sharded(x, shardspec)
+
+
+class TtYOLOv9cConv2D:
+    def __init__(
+        self,
+        conv,
+        conv_pth,
+        bn=None,
+        device=None,
+        cache={},
+        activation="",
+        activation_dtype=ttnn.bfloat8_b,
+        weights_dtype=ttnn.bfloat8_b,
+        use_1d_systolic_array=True,
+        shard_layout=ttnn.TensorMemoryLayout.HEIGHT_SHARDED,
+        is_detect=False,
+        is_dfl=False,
+        config_override=None,
+    ):
+        self.is_detect = is_detect
+        self.is_dfl = is_dfl
+        self.conv = conv
+        self.device = device
+        self.in_channels = conv.in_channels
+        self.out_channels = conv.out_channels
+        self.kernel_size = conv.kernel_size
+        self.padding = conv.padding
+        self.stride = conv.stride
+        self.groups = conv.groups
+        self.use_1d_systolic_array = use_1d_systolic_array
+        self.deallocate_activation = False
+        self.cache = cache
+        self.compute_config = ttnn.init_device_compute_kernel_config(
+            device.arch(),
+            math_fidelity=ttnn.MathFidelity.LoFi,
+            fp32_dest_acc_en=False,
+            packer_l1_acc=False,
+            math_approx_mode=False,
+        )
+        self.conv_config = ttnn.Conv2dConfig(
+            dtype=activation_dtype,
+            weights_dtype=weights_dtype,
+            shard_layout=shard_layout,
+            deallocate_activation=self.deallocate_activation,
+            enable_act_double_buffer=False,
+            enable_split_reader=False,
+            enable_subblock_padding=False,
+            reshard_if_not_optimal=True if self.use_1d_systolic_array else False,
+            activation=activation,
+            input_channels_alignment=32,
+        )
+        config_override = {"act_block_h": 64} if conv.in_channels == 3 else None
+        if config_override and "act_block_h" in config_override:
+            self.conv_config.act_block_h_override = config_override["act_block_h"]
+
+        if "bias" in conv_pth:
+            bias = ttnn.from_device(conv_pth.bias)
+            self.bias = bias
+        else:
+            self.bias = None
+
+        weight = ttnn.from_device(conv_pth.weight)
+        self.weight = weight
+
+    def __call__(self, x):
+        if self.is_detect:
+            input_height = int(math.sqrt(x.shape[2]))
+            input_width = int(math.sqrt(x.shape[2]))
+            batch_size = x.shape[0]
+        elif self.is_dfl:
+            input_height = x.shape[1]
+            input_width = x.shape[2]
+            batch_size = x.shape[0]
+        else:
+            batch_size = self.conv.batch_size
+            input_height = self.conv.input_height
+            input_width = self.conv.input_width
+
+        [x, [output_height, output_width], [self.weight, self.bias]] = ttnn.conv2d(
+            input_tensor=x,
+            weight_tensor=self.weight,
+            bias_tensor=self.bias,
+            device=self.device,
+            in_channels=self.in_channels,
+            out_channels=self.out_channels,
+            input_height=input_height,
+            input_width=input_width,
+            batch_size=batch_size,
+            kernel_size=self.kernel_size,
+            stride=self.stride,
+            padding=self.padding,
+            conv_config=self.conv_config,
+            conv_op_cache=self.cache,
+            groups=self.groups,
+            compute_config=self.compute_config,
+            return_output_dim=True,
+            return_weights_and_bias=True,
+        )
+        hw = output_height * output_width
+        if x.shape[2] != hw:
+            x = ttnn.sharded_to_interleaved(x, ttnn.L1_MEMORY_CONFIG)
+            x = x[:, :, :hw, :]
+        return x
+
+
+class TtnnRepconv:
+    def __init__(self, device, parameter, conv_pt):
+        self.conv1 = TtYOLOv9cConv2D(
+            device=device, conv=parameter.conv1.conv, conv_pth=conv_pt.conv1.conv, activation=""
+        )
+        self.conv2 = TtYOLOv9cConv2D(
+            device=device, conv=parameter.conv2.conv, conv_pth=conv_pt.conv2.conv, activation=""
+        )
+
+    def __call__(self, device, x):
+        conv1_out = self.conv1(x)
+        conv2_out = self.conv2(x)
+        x = ttnn.silu(conv1_out + conv2_out)
+
+        return x
+
+
+class TtnnRepBottleneck:
+    def __init__(self, device, parameter, conv_pt):
+        self.cv1 = TtnnRepconv(device=device, parameter=parameter.cv1, conv_pt=conv_pt.cv1)
+        self.cv2 = TtYOLOv9cConv2D(device=device, conv=parameter.cv2.conv, conv_pth=conv_pt.cv2.conv, activation="silu")
+
+    def __call__(self, device, x):
+        input = x
+        x = self.cv1(device, x)
+        x = self.cv2(x)
+        return input + x
+
+
+class TtnnRepcsp:
+    def __init__(self, device, parameter, conv_pt):
+        self.cv1 = TtYOLOv9cConv2D(device=device, conv=parameter.cv1.conv, conv_pth=conv_pt.cv1.conv, activation="silu")
+        self.cv2 = TtYOLOv9cConv2D(device=device, conv=parameter.cv2.conv, conv_pth=conv_pt.cv2.conv, activation="silu")
+        self.cv3 = TtYOLOv9cConv2D(device=device, conv=parameter.cv3.conv, conv_pth=conv_pt.cv3.conv, activation="silu")
+        self.m = TtnnRepBottleneck(device, parameter.m[0], conv_pt.m[0])
+
+    def __call__(self, device, x):
+        cv1_out = self.cv1(x)
+        m_out = self.m(device, cv1_out)
+        ttnn.deallocate(cv1_out)
+
+        cv2_out = self.cv2(x)
+
+        cv2_out = ttnn.to_layout(cv2_out, ttnn.ROW_MAJOR_LAYOUT)
+        cv2_out = ttnn.to_dtype(cv2_out, ttnn.bfloat16)
+
+        m_out = ttnn.to_layout(m_out, ttnn.ROW_MAJOR_LAYOUT)
+        m_out = ttnn.to_dtype(m_out, ttnn.bfloat16)
+
+        concat_out = concat(-1, True, m_out, cv2_out)
+        ttnn.deallocate(m_out)
+        ttnn.deallocate(cv2_out)
+
+        x = self.cv3(concat_out)
+        ttnn.deallocate(concat_out)
+
+        return x
+
+
+class TtnnRepncspelan4:
+    def __init__(self, device, parameter, conv_pt, shard_layout=ttnn.TensorMemoryLayout.HEIGHT_SHARDED):
+        self.cv1 = TtYOLOv9cConv2D(
+            device=device,
+            conv=parameter.cv1.conv,
+            conv_pth=conv_pt.cv1.conv,
+            activation="silu",
+            shard_layout=shard_layout,
+        )
+        self.k1 = TtnnRepcsp(device, parameter.cv2[0], conv_pt.cv2[0])
+        self.k2 = TtYOLOv9cConv2D(
+            device=device, conv=parameter.cv2[1].conv, conv_pth=conv_pt.cv2[1].conv, activation="silu"
+        )
+        self.k3 = TtnnRepcsp(device, parameter.cv3[0], conv_pt.cv3[0])
+        self.k4 = TtYOLOv9cConv2D(
+            device=device, conv=parameter.cv3[1].conv, conv_pth=conv_pt.cv3[1].conv, activation="silu"
+        )
+        self.cv4 = TtYOLOv9cConv2D(device=device, conv=parameter.cv4.conv, conv_pth=conv_pt.cv4.conv, activation="silu")
+
+    def __call__(self, device, x):
+        x = self.cv1(x)
+        if x.is_sharded():
+            x = ttnn.sharded_to_interleaved(x, memory_config=ttnn.L1_MEMORY_CONFIG)
+
+        y1 = x[:, :, :, : x.shape[-1] // 2]
+        y2 = x[:, :, :, x.shape[-1] // 2 : x.shape[-1]]
+
+        y2 = interleaved_to_sharded(y2)
+
+        cv2_out = self.k1(device, y2)
+        cv3_out = self.k2(cv2_out)
+        ttnn.deallocate(cv2_out)
+
+        cv4_out = self.k3(device, cv3_out)
+        cv5_out = self.k4(cv4_out)
+        ttnn.deallocate(cv4_out)
+
+        x = concat(-1, True, y1, y2, cv3_out, cv5_out)
+        x = self.cv4(x)
+
+        ttnn.deallocate(y1)
+        ttnn.deallocate(y2)
+        ttnn.deallocate(cv3_out)
+        ttnn.deallocate(cv5_out)
+
+        return x
+
+
+class TtnnADown:
+    def __init__(self, device, parameter, conv_pt, use_1d_systolic_array=True):
+        self.parameter = parameter
+        self.cv1 = TtYOLOv9cConv2D(
+            device=device,
+            conv=parameter.cv1.conv,
+            conv_pth=conv_pt.cv1.conv,
+            activation="silu",
+            use_1d_systolic_array=use_1d_systolic_array,
+        )
+        self.cv2 = TtYOLOv9cConv2D(device=device, conv=parameter.cv2.conv, conv_pth=conv_pt.cv2.conv, activation="silu")
+
+    def __call__(self, device, x):
+        if x.shape[1] == 1:
+            x = ttnn.to_layout(x, ttnn.ROW_MAJOR_LAYOUT)
+            x = ttnn.reshape(x, (x.shape[0], int(math.sqrt(x.shape[-2])), int(math.sqrt(x.shape[-2])), x.shape[-1]))
+        if x.is_sharded():
+            x = ttnn.sharded_to_interleaved(x, memory_config=ttnn.L1_MEMORY_CONFIG)
+
+        x = ttnn.permute(x, (0, 3, 1, 2))
+        x = ttnn.to_torch(x)
+        x = torch.nn.functional.avg_pool2d(x, 2, 1, 0, False, True)
+        x = ttnn.from_torch(
+            x, device=device, dtype=ttnn.bfloat16, layout=ttnn.ROW_MAJOR_LAYOUT, memory_config=ttnn.L1_MEMORY_CONFIG
+        )
+        x = ttnn.permute(x, (0, 2, 3, 1))
+
+        x1 = x[:, :, :, : x.shape[-1] // 2]
+        x2 = x[:, :, :, x.shape[-1] // 2 : x.shape[-1]]
+        ttnn.deallocate(x)
+
+        x1 = self.cv1(x1)
+
+        x2 = ttnn.reshape(x2, (1, 1, x2.shape[0] * x2.shape[1] * x2.shape[2], x2.shape[-1]))
+        x2_maxpool = ttnn.max_pool2d(
+            x2,
+            batch_size=self.parameter.cv1.conv.batch_size,
+            input_h=self.parameter.cv1.conv.input_height,
+            input_w=self.parameter.cv1.conv.input_width,
+            channels=self.parameter.cv1.conv.in_channels,
+            kernel_size=[3, 3],
+            stride=[2, 2],
+            padding=[1, 1],
+            dilation=[1, 1],
+        )
+        x2 = self.cv2(x2_maxpool)
+        x = concat(-1, True, x1, x2)
+
+        ttnn.deallocate(x1)
+        ttnn.deallocate(x2)
+
+        return x
+
+
+class TtnnSPPELAN:
+    def __init__(self, device, parameter, conv_pt):
+        self.parameter = parameter
+        self.cv1 = TtYOLOv9cConv2D(device=device, conv=parameter.cv1.conv, conv_pth=conv_pt.cv1.conv, activation="silu")
+        self.cv5 = TtYOLOv9cConv2D(device=device, conv=parameter.cv5.conv, conv_pth=conv_pt.cv5.conv, activation="silu")
+
+    def __call__(self, device, x):
+        x = self.cv1(x)
+        x1 = x
+        if x.is_sharded():
+            x = ttnn.sharded_to_interleaved(x)
+
+        TILE_WIDTH = 32
+        in_c = self.parameter.cv5.conv.in_channels
+        in_c_padded = in_c
+        if in_c % TILE_WIDTH != 0 and in_c != 16:
+            in_c_padded = in_c + (TILE_WIDTH - in_c % TILE_WIDTH)
+        m1 = ttnn.max_pool2d(
+            x,
+            batch_size=self.parameter.cv5.conv.batch_size,
+            input_h=self.parameter.cv5.conv.input_height,
+            input_w=self.parameter.cv5.conv.input_width,
+            channels=in_c_padded,
+            kernel_size=[5, 5],
+            stride=[1, 1],
+            padding=[2, 2],
+            dilation=[1, 1],
+            applied_shard_scheme=ttnn.TensorMemoryLayout.BLOCK_SHARDED,
+        )
+
+        m2 = ttnn.max_pool2d(
+            m1,
+            batch_size=self.parameter.cv5.conv.batch_size,
+            input_h=self.parameter.cv5.conv.input_height,
+            input_w=self.parameter.cv5.conv.input_width,
+            channels=in_c_padded,
+            kernel_size=[5, 5],
+            stride=[1, 1],
+            padding=[2, 2],
+            dilation=[1, 1],
+        )
+        m3 = ttnn.max_pool2d(
+            m2,
+            batch_size=self.parameter.cv5.conv.batch_size,
+            input_h=self.parameter.cv5.conv.input_height,
+            input_w=self.parameter.cv5.conv.input_width,
+            channels=in_c_padded,
+            kernel_size=[5, 5],
+            stride=[1, 1],
+            padding=[2, 2],
+            dilation=[1, 1],
+        )
+
+        y = concat(-1, True, x1, m1, m2, m3)
+        ttnn.deallocate(x1)
+        ttnn.deallocate(m1)
+        ttnn.deallocate(m2)
+        ttnn.deallocate(m3)
+
+        x = self.cv5(y)
+
+        return x
+
+
+class TtnnDetect:
+    def __init__(self, device, parameter, conv_pt):
+        self.cv2_0_0 = TtYOLOv9cConv2D(
+            device=device,
+            conv=parameter.cv2[0][0].conv,
+            conv_pth=conv_pt.cv2[0][0].conv,
+            activation="silu",
+            is_detect=True,
+        )
+        self.cv2_0_1 = TtYOLOv9cConv2D(
+            device=device,
+            conv=parameter.cv2[0][1].conv,
+            conv_pth=conv_pt.cv2[0][1].conv,
+            activation="silu",
+            is_detect=True,
+        )
+        self.cv2_0_2 = TtYOLOv9cConv2D(
+            conv=parameter.cv2[0][2], conv_pth=conv_pt.cv2[0][2], device=device, is_detect=True
+        )
+
+        self.cv2_1_0 = TtYOLOv9cConv2D(
+            device=device,
+            conv=parameter.cv2[1][0].conv,
+            conv_pth=conv_pt.cv2[1][0].conv,
+            activation="silu",
+            is_detect=True,
+        )
+        self.cv2_1_1 = TtYOLOv9cConv2D(
+            device=device,
+            conv=parameter.cv2[1][1].conv,
+            conv_pth=conv_pt.cv2[1][1].conv,
+            activation="silu",
+            is_detect=True,
+        )
+        self.cv2_1_2 = TtYOLOv9cConv2D(
+            conv=parameter.cv2[1][2], conv_pth=conv_pt.cv2[1][2], device=device, is_detect=True
+        )
+
+        self.cv2_2_0 = TtYOLOv9cConv2D(
+            device=device,
+            conv=parameter.cv2[2][0].conv,
+            conv_pth=conv_pt.cv2[2][0].conv,
+            activation="silu",
+            is_detect=True,
+        )
+        self.cv2_2_1 = TtYOLOv9cConv2D(
+            device=device,
+            conv=parameter.cv2[2][1].conv,
+            conv_pth=conv_pt.cv2[2][1].conv,
+            is_detect=True,
+        )
+        self.cv2_2_2 = TtYOLOv9cConv2D(
+            conv=parameter.cv2[2][2], conv_pth=conv_pt.cv2[2][2], device=device, is_detect=True
+        )
+
+        self.cv3_0_0 = TtYOLOv9cConv2D(
+            device=device,
+            conv=parameter.cv3[0][0].conv,
+            conv_pth=conv_pt.cv3[0][0].conv,
+            activation="silu",
+            is_detect=True,
+        )
+        self.cv3_0_1 = TtYOLOv9cConv2D(
+            device=device,
+            conv=parameter.cv3[0][1].conv,
+            conv_pth=conv_pt.cv3[0][1].conv,
+            activation="silu",
+            is_detect=True,
+        )
+        self.cv3_0_2 = TtYOLOv9cConv2D(
+            conv=parameter.cv3[0][2], conv_pth=conv_pt.cv3[0][2], device=device, is_detect=True
+        )
+
+        self.cv3_1_0 = TtYOLOv9cConv2D(
+            device=device,
+            conv=parameter.cv3[1][0].conv,
+            conv_pth=conv_pt.cv3[1][0].conv,
+            activation="silu",
+            is_detect=True,
+        )
+        self.cv3_1_1 = TtYOLOv9cConv2D(
+            device=device,
+            conv=parameter.cv3[1][1].conv,
+            conv_pth=conv_pt.cv3[1][1].conv,
+            activation="silu",
+            is_detect=True,
+        )
+        self.cv3_1_2 = TtYOLOv9cConv2D(
+            conv=parameter.cv3[1][2], conv_pth=conv_pt.cv3[1][2], device=device, is_detect=True
+        )
+
+        self.cv3_2_0 = TtYOLOv9cConv2D(
+            device=device,
+            conv=parameter.cv3[2][0].conv,
+            conv_pth=conv_pt.cv3[2][0].conv,
+            activation="silu",
+            is_detect=True,
+            shard_layout=ttnn.TensorMemoryLayout.BLOCK_SHARDED,
+        )
+        self.cv3_2_1 = TtYOLOv9cConv2D(
+            device=device,
+            conv=parameter.cv3[2][1].conv,
+            conv_pth=conv_pt.cv3[2][1].conv,
+            activation="silu",
+            is_detect=True,
+        )
+        self.cv3_2_2 = TtYOLOv9cConv2D(
+            conv=parameter.cv3[2][2], conv_pth=conv_pt.cv3[2][2], device=device, is_detect=True
+        )
+        self.dfl = TtYOLOv9cConv2D(device=device, conv=parameter.dfl.conv, conv_pth=conv_pt.dfl.conv, is_dfl=True)
+        self.anchors = conv_pt.anchors
+        self.strides = conv_pt.strides
+
+    def __call__(self, device, y1, y2, y3):
+        x1 = self.cv2_0_0(y1)
+        x1 = self.cv2_0_1(x1)
+        x1 = self.cv2_0_2(x1)
+        x2 = self.cv2_1_0(y2)
+        x2 = self.cv2_1_1(x2)
+        x2 = self.cv2_1_2(x2)
+        x3 = self.cv2_2_0(y3)
+        x3 = self.cv2_2_1(x3)
+        x3 = self.cv2_2_2(x3)
+        x4 = self.cv3_0_0(y1)
+        x4 = self.cv3_0_1(x4)
+        x4 = self.cv3_0_2(x4)
+        x5 = self.cv3_1_0(y2)
+        x5 = self.cv3_1_1(x5)
+        x5 = self.cv3_1_2(x5)
+        x6 = self.cv3_2_0(y3)
+        x6 = self.cv3_2_1(x6)
+        x6 = self.cv3_2_2(x6)
+
+        y1 = concat(-1, False, x1, x4)
+        y2 = concat(-1, False, x2, x5)
+        y3 = concat(-1, False, x3, x6)
+
+        ttnn.deallocate(x1)
+        ttnn.deallocate(x2)
+        ttnn.deallocate(x3)
+        ttnn.deallocate(x4)
+        ttnn.deallocate(x5)
+        ttnn.deallocate(x6)
+
+        y = concat(2, False, y1, y2, y3)
+
+        ttnn.deallocate(y1)
+        ttnn.deallocate(y2)
+        ttnn.deallocate(y3)
+
+        ya, yb = y[:, :, :, :64], y[:, :, :, 64:144]
+        ya = ttnn.permute(ya, (0, 1, 3, 2))
+        ya = ttnn.reshape(ya, (ya.shape[0], 4, 16, ya.shape[-1]))
+        ya = ttnn.permute(ya, (0, 2, 1, 3))
+        ya = ttnn.to_layout(ya, ttnn.TILE_LAYOUT)
+        ya = ttnn.softmax(ya, dim=1, memory_config=ttnn.L1_MEMORY_CONFIG)
+        ya = ttnn.permute(ya, (0, 2, 3, 1))
+        c = self.dfl(ya)
+
+        if c.is_sharded():
+            c = ttnn.sharded_to_interleaved(c, memory_config=ttnn.L1_MEMORY_CONFIG)
+        c = ttnn.to_layout(c, layout=ttnn.ROW_MAJOR_LAYOUT)
+        c = ttnn.permute(c, (0, 3, 1, 2))
+        c = ttnn.reshape(c, (c.shape[0], 1, 4, int(c.shape[3] / 4)))
+        c = ttnn.reshape(c, (c.shape[0], c.shape[1] * c.shape[2], c.shape[3]))
+        c1, c2 = c[:, :2, :], c[:, 2:4, :]
+
+        c1 = ttnn.to_layout(c1, layout=ttnn.TILE_LAYOUT)
+        c2 = ttnn.to_layout(c2, layout=ttnn.TILE_LAYOUT)
+
+        c1 = self.anchors - c1
+        c2 = self.anchors + c2
+        z1 = c2 - c1
+        z2 = c1 + c2
+        z2 = ttnn.div(z2, 2)
+
+        ttnn.deallocate(ya)
+        ttnn.deallocate(c)
+        ttnn.deallocate(c1)
+        ttnn.deallocate(c2)
+
+        z = concat(1, False, z2, z1)
+        z = ttnn.multiply(z, self.strides, memory_config=ttnn.L1_MEMORY_CONFIG)
+        yb = ttnn.squeeze(yb, 0)
+        yb = ttnn.permute(yb, (0, 2, 1))
+        yb = ttnn.sigmoid(yb)
+
+        out = concat(1, False, z, yb)
+
+        ttnn.deallocate(z)
+        ttnn.deallocate(yb)
+
+        return out
+
+
+class YoloV9:
+    def __init__(self, device, parameters):
+        self.device = device
+        self.conv1 = TtYOLOv9cConv2D(
+            device=device,
+            conv=parameters.conv_args[0].conv,
+            conv_pth=parameters.model[0].conv,
+            config_override={"act_block_h": 32},
+            activation="silu",
+        )  # 0
+        self.conv2 = TtYOLOv9cConv2D(
+            device=device, conv=parameters.conv_args[1].conv, conv_pth=parameters.model[1].conv, activation="silu"
+        )  # 1
+        self.repncspelan4_1 = TtnnRepncspelan4(device, parameters.conv_args[2], parameters.model[2])  # 2
+        self.adown_1 = TtnnADown(device, parameters.conv_args[3], parameters.model[3], use_1d_systolic_array=False)  # 3
+        self.repncspelan4_2 = TtnnRepncspelan4(device, parameters.conv_args[4], parameters.model[4])  # 4
+        self.adown_2 = TtnnADown(device, parameters.conv_args[5], parameters.model[5])  # 5
+        self.repncspelan4_3 = TtnnRepncspelan4(device, parameters.conv_args[6], parameters.model[6])  # 6
+        self.adown_3 = TtnnADown(device, parameters.conv_args[7], parameters.model[7])  # 7
+        self.repncspelan4_4 = TtnnRepncspelan4(device, parameters.conv_args[8], parameters.model[8])  # 8
+        self.ttnn_sppelan = TtnnSPPELAN(device, parameters.conv_args[9], parameters.model[9])  # 9
+        self.repncspelan4_5 = TtnnRepncspelan4(device, parameters.conv_args[12], parameters.model[12])  # 12
+        self.repncspelan4_6 = TtnnRepncspelan4(device, parameters.conv_args[15], parameters.model[15])  # 15
+        self.adown_6 = TtnnADown(device, parameters.conv_args[16], parameters.model[16])  # 16
+        self.repncspelan4_7 = TtnnRepncspelan4(device, parameters.conv_args[18], parameters.model[18])  # 18
+        self.adown_7 = TtnnADown(device, parameters.conv_args[19], parameters.model[19])  # 19
+        self.repncspelan4_8 = TtnnRepncspelan4(device, parameters.conv_args[21], parameters.model[21])  # 21
+        self.detect = TtnnDetect(device, parameters.model_args.model[22], parameters.model[22])  # 22
+
+    def __call__(self, x):
+        x = self.conv1(x)  # 0
+        x = self.conv2(x)  # 1
+        x = self.repncspelan4_1(self.device, x)  # 2
+        x = self.adown_1(self.device, x)  # 3
+        x = self.repncspelan4_2(self.device, x)  # 4
+        x4 = x
+        x = self.adown_2(self.device, x)  # 5
+        x = self.repncspelan4_3(self.device, x)  # 6
+        x6 = x
+        x = self.adown_3(self.device, x)  # 7
+        x = self.repncspelan4_4(self.device, x)  # 8
+        x = self.ttnn_sppelan(self.device, x)  # 9
+        x10 = x
+        x = interleaved_to_sharded(x)
+        x = ttnn.upsample(x, scale_factor=2)  # 10
+
+        x = ttnn.reshape(x, (1, 1, x.shape[0] * x.shape[1] * x.shape[2], x.shape[3]))
+        x = concat(-1, True, x, x6)  # 11
+        ttnn.deallocate(x6)
+
+        x = self.repncspelan4_5(self.device, x)  # 12
+        x13 = x
+        x = interleaved_to_sharded(x)
+        x = ttnn.upsample(x, scale_factor=2)  # 13
+
+        x = ttnn.reshape(x, (1, 1, x.shape[0] * x.shape[1] * x.shape[2], x.shape[3]))
+
+        x = concat(-1, True, x, x4)  # 14
+        x = ttnn.sharded_to_interleaved(x)
+        ttnn.deallocate(x4)
+
+        x = self.repncspelan4_6(self.device, x)  # 15
+        x16 = x
+        x = self.adown_6(self.device, x)  # 16
+
+        x = concat(-1, False, x, x13)  # 17
+        ttnn.deallocate(x13)
+
+        x = self.repncspelan4_7(self.device, x)  # 18
+        x19 = x
+        x = self.adown_7(self.device, x)  # 19
+        x = concat(-1, True, x, x10)  # 20
+        ttnn.deallocate(x10)
+
+        x = self.repncspelan4_8(self.device, x)  # 21
+        x22 = x
+        x = self.detect(self.device, x16, x19, x22)  # 22
+
+        ttnn.deallocate(x16)
+        ttnn.deallocate(x19)
+        ttnn.deallocate(x22)
+
+        return x
diff --git a/models/experimental/yolo_common/yolo_utils.py b/models/experimental/yolo_common/yolo_utils.py
index 788f8028c2ca..f1c654ceb7b3 100644
--- a/models/experimental/yolo_common/yolo_utils.py
+++ b/models/experimental/yolo_common/yolo_utils.py
@@ -40,3 +40,42 @@ def get_core_grid_from_num_cores(num_cores: int, grid_rows: int = 8, grid_cols:
             )
         )
     return ttnn.CoreRangeSet({*ranges})
+
+def sharded_concat(input_tensors, num_cores=56, dim=3):
+    shard_grid = get_core_grid_from_num_cores(num_cores=num_cores)
+    in_shard_width = input_tensors[0].shape[-1]
+    shard_height = (input_tensors[0].shape[2] + num_cores - 1) // num_cores
+    input_sharded_memory_config = ttnn.create_sharded_memory_config_(
+        (shard_height, in_shard_width),
+        core_grid=shard_grid,
+        strategy=ttnn.ShardStrategy.HEIGHT,
+        orientation=ttnn.ShardOrientation.ROW_MAJOR,
+        use_height_and_width_as_shard_shape=True,
+    )
+    out_shard_width = 0
+    for i in range(len(input_tensors)):
+        out_shard_width += input_tensors[i].shape[-1]
+        input_tensors[i] = ttnn.to_memory_config(input_tensors[i], input_sharded_memory_config)
+
+    output_sharded_memory_config = ttnn.create_sharded_memory_config_(
+        (shard_height, out_shard_width),
+        core_grid=shard_grid,
+        strategy=ttnn.ShardStrategy.HEIGHT,
+        orientation=ttnn.ShardOrientation.ROW_MAJOR,
+        use_height_and_width_as_shard_shape=True,
+    )
+    output = ttnn.concat(input_tensors, dim, memory_config=output_sharded_memory_config)
+    return output
+
+
+def concat(dim=-1, use_sharded_concat=True, *tensors):
+    if use_sharded_concat:
+        processed_tensors = [
+            ttnn.to_dtype(ttnn.to_layout(tensor, ttnn.ROW_MAJOR_LAYOUT), ttnn.bfloat16) for tensor in tensors
+        ]
+        return sharded_concat(processed_tensors)
+    else:
+        processed_tensors = [
+            ttnn.sharded_to_interleaved(tensor) if tensor.is_sharded() else tensor for tensor in tensors
+        ]
+        return ttnn.concat(processed_tensors, dim=dim, memory_config=ttnn.L1_MEMORY_CONFIG)
diff --git a/tests/ttnn/integration_tests/yolov9c/test_ttnn_yolov9c.py b/tests/ttnn/integration_tests/yolov9c/test_ttnn_yolov9c.py
new file mode 100644
index 000000000000..0a8ac9bda710
--- /dev/null
+++ b/tests/ttnn/integration_tests/yolov9c/test_ttnn_yolov9c.py
@@ -0,0 +1,66 @@
+# SPDX-FileCopyrightText: © 2025 Tenstorrent Inc.
+
+# SPDX-License-Identifier: Apache-2.0
+
+import os
+import sys
+import ttnn
+import torch
+import pickle
+import pytest
+import torch.nn as nn
+from models.utility_functions import run_for_wormhole_b0
+from tests.ttnn.utils_for_testing import assert_with_pcc
+from models.experimental.functional_yolov9c.tt.model_preprocessing import (
+    create_yolov9c_input_tensors,
+    create_yolov9c_model_parameters,
+    create_yolov9c_model_parameters_detect,
+)
+from models.experimental.functional_yolov9c.tt import ttnn_yolov9c
+from models.experimental.functional_yolov9c.reference import yolov9c
+from models.experimental.functional_yolov9c.demo.demo_utils import attempt_load
+
+try:
+    sys.modules["ultralytics"] = yolov9c
+    sys.modules["ultralytics.nn.tasks"] = yolov9c
+    sys.modules["ultralytics.nn.modules.conv"] = yolov9c
+    sys.modules["ultralytics.nn.modules.block"] = yolov9c
+    sys.modules["ultralytics.nn.modules.head"] = yolov9c
+
+except KeyError:
+    logger.error("models.experimental.functional_yolov9c.reference.yolov9c not found.")
+
+
+@run_for_wormhole_b0()
+@pytest.mark.parametrize(
+    "use_pretrained_weight",
+    [
+        # False,
+        True
+    ],
+)
+@pytest.mark.parametrize("device_params", [{"l1_small_size": 79104}], indirect=True)
+def test_yolov9c(use_pretrained_weight, device, use_program_cache, reset_seeds):
+    torch_input, ttnn_input = create_yolov9c_input_tensors(device, model=True)
+    state_dict = None
+
+    if use_pretrained_weight:
+        torch_model = attempt_load("yolov9c.pt", map_location="cpu")
+        state_dict = torch_model.state_dict()
+
+    torch_model = yolov9c.YoloV9()
+    state_dict = torch_model.state_dict() if state_dict is None else state_dict
+    ds_state_dict = {k: v for k, v in state_dict.items()}
+    new_state_dict = {}
+    for (name1, parameter1), (name2, parameter2) in zip(torch_model.state_dict().items(), ds_state_dict.items()):
+        if isinstance(parameter2, torch.FloatTensor):
+            new_state_dict[name1] = parameter2
+    torch_model.load_state_dict(new_state_dict)
+    torch_model.eval()
+    torch_output = torch_model(torch_input)
+    parameters = create_yolov9c_model_parameters(torch_model, torch_input, device)
+    ttnn_model = ttnn_yolov9c.YoloV9(device, parameters)
+    ttnn_output = ttnn_model(ttnn_input)
+    ttnn_output = ttnn.to_torch(ttnn_output)
+
+    assert_with_pcc(torch_output, ttnn_output, 0.99)


Reviews:
user: login: bbradelTT
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-21T11:50:34Z
user: login: kkeerthana0573
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-21T12:47:41Z
user: login: kkeerthana0573
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-21T12:49:15Z
user: login: TT-BrianLiu
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-23T21:24:21Z
user: login: mbahnasTT
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-23T21:34:37Z
user: login: esmalTT
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-23T23:12:24Z
user: login: kkeerthana0573
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-24T05:07:46Z
user: login: kkeerthana0573
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-24T18:13:49Z
user: login: kkeerthana0573
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-24T18:21:10Z
user: login: kkeerthana0573
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-24T18:21:54Z
user: login: kkeerthana0573
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-24T18:22:53Z
user: login: kkeerthana0573
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-24T18:23:10Z
user: login: kkeerthana0573
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-24T18:23:27Z
user: login: bbradelTT
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-24T20:04:46Z
user: login: ssinghalTT
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-24T20:14:09Z
user: login: esmalTT
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-24T22:24:22Z
user: login: kkeerthana0573
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-25T09:49:11Z
user: login: kkeerthana0573
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-25T09:49:16Z
user: login: esmalTT
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-25T15:15:06Z

Review comments:
diff_hunk: @@ -0,0 +1,33 @@
+# Yolov9c Demo
+Demo showcasing Yolov9c running on Wormhole - n150, n300 using ttnn.
+
+## Platforms:
+    WH N150, N300
+
+## Introduction:
+Yolov9 marks a significant advancement in real-time object detection, introducing groundbreaking techniques such as Programmable Gradient Information (PGI) and the Generalized Efficient Layer Aggregation Network (GELAN). Yolov9c is a compact and optimized variant of Yolov9, designed for efficient object detection with reduced computational overhead. It balances speed and accuracy.
+
+## Details:
+The entry point to functional_yolov9c model is YoloV9 in `models/experimental/functional_yolov9c/tt/ttnn_yolov9c.py`. The model picks up certain configs and weights from Ultralytics pretrained model. We've used weights available [here](https://docs.ultralytics.com/models/yolov9/#performance-on-ms-coco-dataset) under YOLOv9c.
+
+<!-- ## Inputs:
path: models/experimental/functional_yolov9c/README.md
user: login: bbradelTT
body: Why is this text commented out?
created_at: 2025-03-21T11:44:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,443 @@
+# SPDX-FileCopyrightText: © 2025 Tenstorrent Inc.
+
+# SPDX-License-Identifier: Apache-2.0
+
+import os
+import cv2
+import math
+import time
+import torch
+import requests
+import torchvision
+import numpy as np
+import torch.nn as nn
+from pathlib import Path
+from loguru import logger
+from datetime import datetime
+from functools import partial
+
+
+def imread(filename: str, flags: int = cv2.IMREAD_COLOR):
+    return cv2.imdecode(np.fromfile(filename, np.uint8), flags)
+
+
+IMG_FORMATS = {"bmp", "dng", "jpeg", "jpg", "mpo", "png", "tif", "tiff", "webp", "pfm", "heic"}
+
+
+class LoadImages:
+    def __init__(self, path, batch=1, vid_stride=1):
+        files = []
+        for p in sorted(path) if isinstance(path, (list, tuple)) else [path]:
+            a = str(Path(p).absolute())
+            if os.path.isfile(a):
+                files.append(a)
+            else:
+                raise FileNotFoundError(f"{p} does not exist")
+
+        images = []
+        for f in files:
+            suffix = f.split(".")[-1].lower()
+            if suffix in IMG_FORMATS:
+                images.append(f)
+        ni = len(images)
+
+        self.files = images
+        self.nf = ni
+        self.ni = ni
+        self.mode = "image"
+        self.vid_stride = vid_stride
+        self.bs = batch
+        if self.nf == 0:
+            raise FileNotFoundError(f"No images or videos found in {p}")
+
+    def __iter__(self):
+        self.count = 0
+        return self
+
+    def __next__(self):
+        paths, imgs, info = [], [], []
+        while len(imgs) < self.bs:
+            if self.count >= self.nf:
+                if imgs:
+                    return paths, imgs, info
+                else:
+                    raise StopIteration
+
+            path = self.files[self.count]
+
+            self.mode = "image"
+            im0 = imread(path)
+            if im0 is None:
+                logger.warning(f"WARNING ⚠️ Image Read Error {path}")
+            else:
+                paths.append(path)
+                imgs.append(im0)
+                info.append(f"image {self.count + 1}/{self.nf} {path}: ")
+            self.count += 1
+            if self.count >= self.ni:
+                break
+
+        return paths, imgs, info
+
+    def _new_video(self, path):
+        self.frame = 0
+        self.cap = cv2.VideoCapture(path)
+        self.fps = int(self.cap.get(cv2.CAP_PROP_FPS))
+        if not self.cap.isOpened():
+            raise FileNotFoundError(f"Failed to open video {path}")
+        self.frames = int(self.cap.get(cv2.CAP_PROP_FRAME_COUNT) / self.vid_stride)
+
+    def __len__(self):
+        return math.ceil(self.nf / self.bs)
+
+
+def LetterBox(img, new_shape=(640, 640), auto=False, scaleFill=False, scaleup=True, center=True, stride=32):
+    shape = img.shape[:2]
+    if isinstance(new_shape, int):
+        new_shape = (new_shape, new_shape)
+
+    r = min(new_shape[0] / shape[0], new_shape[1] / shape[1])
+    if not scaleup:
+        r = min(r, 1.0)
+
+    new_unpad = int(round(shape[1] * r)), int(round(shape[0] * r))
+    dw, dh = new_shape[1] - new_unpad[0], new_shape[0] - new_unpad[1]
+    if auto:
+        dw, dh = np.mod(dw, stride), np.mod(dh, stride)
+
+    if center:
+        dw /= 2
+        dh /= 2
+
+    if shape[::-1] != new_unpad:
+        img = cv2.resize(img, new_unpad, interpolation=cv2.INTER_LINEAR)
+    top, bottom = int(round(dh - 0.1)) if center else 0, int(round(dh + 0.1))
+    left, right = int(round(dw - 0.1)) if center else 0, int(round(dw + 0.1))
+    img = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_CONSTANT, value=(114, 114, 114))
+
+    return img
+
+
+def pre_transform(im):
+    return [LetterBox(img=x) for x in im]
+
+
+def preprocess(im):
+    device = "cpu"
+    not_tensor = not isinstance(im, torch.Tensor)
+    if not_tensor:
+        im = np.stack(pre_transform(im))
+        im = im[..., ::-1].transpose((0, 3, 1, 2))
+        im = np.ascontiguousarray(im)
+        im = torch.from_numpy(im)
+
+    im = im.half() if device != "cpu" else im.float()
+    if not_tensor:
+        im /= 255
+    return im
+
+
+def empty_like(x):
+    return (
+        torch.empty_like(x, dtype=torch.float32) if isinstance(x, torch.Tensor) else np.empty_like(x, dtype=np.float32)
+    )
+
+
+def postprocess(preds, img, orig_imgs, batch, names):
+    args = {"conf": 0.5, "iou": 0.7, "agnostic_nms": False, "max_det": 300, "classes": None}
+
+    preds = non_max_suppression(
+        preds,
+        args["conf"],
+        args["iou"],
+        agnostic=args["agnostic_nms"],
+        max_det=args["max_det"],
+        classes=args["classes"],
+    )
+
+    results = []
+    for pred, orig_img, img_path in zip(preds, orig_imgs, batch[0]):
+        pred[:, :4] = scale_boxes(img.shape[2:], pred[:, :4], orig_img.shape)
+        results.append(Results(orig_img, path=img_path, names=names, boxes=pred))
+
+    return results
+
+
+def empty_like(x):
+    return (
+        torch.empty_like(x, dtype=torch.float32) if isinstance(x, torch.Tensor) else np.empty_like(x, dtype=np.float32)
+    )
+
+
+def xywh2xyxy(x):
+    assert x.shape[-1] == 4, f"input shape last dimension expected 4 but input shape is {x.shape}"
+    y = empty_like(x)
+    xy = x[..., :2]
+    wh = x[..., 2:] / 2
+    y[..., :2] = xy - wh
+    y[..., 2:] = xy + wh
+    return y
+
+
+def non_max_suppression(
+    prediction,
+    conf_thres=0.25,
+    iou_thres=0.45,
+    classes=None,
+    agnostic=False,
+    multi_label=False,
+    labels=(),
+    max_det=300,
+    nc=0,
+    max_time_img=0.05,
+    max_nms=30000,
+    max_wh=7680,
+    in_place=True,
+    rotated=False,
+):
+    assert 0 <= conf_thres <= 1, f"Invalid Confidence threshold {conf_thres}, valid values are between 0.0 and 1.0"
+    assert 0 <= iou_thres <= 1, f"Invalid IoU {iou_thres}, valid values are between 0.0 and 1.0"
+
+    if isinstance(prediction, (list, tuple)):
+        prediction = prediction[0]
+    if classes is not None:
+        classes = torch.tensor(classes, device=prediction.device)
+
+    if prediction.shape[-1] == 6:
+        output = [pred[pred[:, 4] > conf_thres][:max_det] for pred in prediction]
+        if classes is not None:
+            output = [pred[(pred[:, 5:6] == classes).any(1)] for pred in output]
+        return output
+
+    bs = prediction.shape[0]
+    nc = nc or (prediction.shape[1] - 4)
+    nm = prediction.shape[1] - nc - 4
+    mi = 4 + nc
+    xc = prediction[:, 4:mi].amax(1) > conf_thres
path: models/experimental/functional_yolov9c/demo/demo_utils.py
user: login: bbradelTT
body: Please expand the two letter variables into better named ones where it makes sense, here and above.
created_at: 2025-03-21T11:50:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,33 @@
+# Yolov9c Demo
+Demo showcasing Yolov9c running on Wormhole - n150, n300 using ttnn.
+
+## Platforms:
+    WH N150, N300
+
+## Introduction:
+Yolov9 marks a significant advancement in real-time object detection, introducing groundbreaking techniques such as Programmable Gradient Information (PGI) and the Generalized Efficient Layer Aggregation Network (GELAN). Yolov9c is a compact and optimized variant of Yolov9, designed for efficient object detection with reduced computational overhead. It balances speed and accuracy.
+
+## Details:
+The entry point to functional_yolov9c model is YoloV9 in `models/experimental/functional_yolov9c/tt/ttnn_yolov9c.py`. The model picks up certain configs and weights from Ultralytics pretrained model. We've used weights available [here](https://docs.ultralytics.com/models/yolov9/#performance-on-ms-coco-dataset) under YOLOv9c.
+
+<!-- ## Inputs:
path: models/experimental/functional_yolov9c/README.md
user: login: kkeerthana0573
body: The lines were commented as the demo wasn't part of this PR. 
I've now removed them.
created_at: 2025-03-21T12:47:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,443 @@
+# SPDX-FileCopyrightText: © 2025 Tenstorrent Inc.
+
+# SPDX-License-Identifier: Apache-2.0
+
+import os
+import cv2
+import math
+import time
+import torch
+import requests
+import torchvision
+import numpy as np
+import torch.nn as nn
+from pathlib import Path
+from loguru import logger
+from datetime import datetime
+from functools import partial
+
+
+def imread(filename: str, flags: int = cv2.IMREAD_COLOR):
+    return cv2.imdecode(np.fromfile(filename, np.uint8), flags)
+
+
+IMG_FORMATS = {"bmp", "dng", "jpeg", "jpg", "mpo", "png", "tif", "tiff", "webp", "pfm", "heic"}
+
+
+class LoadImages:
+    def __init__(self, path, batch=1, vid_stride=1):
+        files = []
+        for p in sorted(path) if isinstance(path, (list, tuple)) else [path]:
+            a = str(Path(p).absolute())
+            if os.path.isfile(a):
+                files.append(a)
+            else:
+                raise FileNotFoundError(f"{p} does not exist")
+
+        images = []
+        for f in files:
+            suffix = f.split(".")[-1].lower()
+            if suffix in IMG_FORMATS:
+                images.append(f)
+        ni = len(images)
+
+        self.files = images
+        self.nf = ni
+        self.ni = ni
+        self.mode = "image"
+        self.vid_stride = vid_stride
+        self.bs = batch
+        if self.nf == 0:
+            raise FileNotFoundError(f"No images or videos found in {p}")
+
+    def __iter__(self):
+        self.count = 0
+        return self
+
+    def __next__(self):
+        paths, imgs, info = [], [], []
+        while len(imgs) < self.bs:
+            if self.count >= self.nf:
+                if imgs:
+                    return paths, imgs, info
+                else:
+                    raise StopIteration
+
+            path = self.files[self.count]
+
+            self.mode = "image"
+            im0 = imread(path)
+            if im0 is None:
+                logger.warning(f"WARNING ⚠️ Image Read Error {path}")
+            else:
+                paths.append(path)
+                imgs.append(im0)
+                info.append(f"image {self.count + 1}/{self.nf} {path}: ")
+            self.count += 1
+            if self.count >= self.ni:
+                break
+
+        return paths, imgs, info
+
+    def _new_video(self, path):
+        self.frame = 0
+        self.cap = cv2.VideoCapture(path)
+        self.fps = int(self.cap.get(cv2.CAP_PROP_FPS))
+        if not self.cap.isOpened():
+            raise FileNotFoundError(f"Failed to open video {path}")
+        self.frames = int(self.cap.get(cv2.CAP_PROP_FRAME_COUNT) / self.vid_stride)
+
+    def __len__(self):
+        return math.ceil(self.nf / self.bs)
+
+
+def LetterBox(img, new_shape=(640, 640), auto=False, scaleFill=False, scaleup=True, center=True, stride=32):
+    shape = img.shape[:2]
+    if isinstance(new_shape, int):
+        new_shape = (new_shape, new_shape)
+
+    r = min(new_shape[0] / shape[0], new_shape[1] / shape[1])
+    if not scaleup:
+        r = min(r, 1.0)
+
+    new_unpad = int(round(shape[1] * r)), int(round(shape[0] * r))
+    dw, dh = new_shape[1] - new_unpad[0], new_shape[0] - new_unpad[1]
+    if auto:
+        dw, dh = np.mod(dw, stride), np.mod(dh, stride)
+
+    if center:
+        dw /= 2
+        dh /= 2
+
+    if shape[::-1] != new_unpad:
+        img = cv2.resize(img, new_unpad, interpolation=cv2.INTER_LINEAR)
+    top, bottom = int(round(dh - 0.1)) if center else 0, int(round(dh + 0.1))
+    left, right = int(round(dw - 0.1)) if center else 0, int(round(dw + 0.1))
+    img = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_CONSTANT, value=(114, 114, 114))
+
+    return img
+
+
+def pre_transform(im):
+    return [LetterBox(img=x) for x in im]
+
+
+def preprocess(im):
+    device = "cpu"
+    not_tensor = not isinstance(im, torch.Tensor)
+    if not_tensor:
+        im = np.stack(pre_transform(im))
+        im = im[..., ::-1].transpose((0, 3, 1, 2))
+        im = np.ascontiguousarray(im)
+        im = torch.from_numpy(im)
+
+    im = im.half() if device != "cpu" else im.float()
+    if not_tensor:
+        im /= 255
+    return im
+
+
+def empty_like(x):
+    return (
+        torch.empty_like(x, dtype=torch.float32) if isinstance(x, torch.Tensor) else np.empty_like(x, dtype=np.float32)
+    )
+
+
+def postprocess(preds, img, orig_imgs, batch, names):
+    args = {"conf": 0.5, "iou": 0.7, "agnostic_nms": False, "max_det": 300, "classes": None}
+
+    preds = non_max_suppression(
+        preds,
+        args["conf"],
+        args["iou"],
+        agnostic=args["agnostic_nms"],
+        max_det=args["max_det"],
+        classes=args["classes"],
+    )
+
+    results = []
+    for pred, orig_img, img_path in zip(preds, orig_imgs, batch[0]):
+        pred[:, :4] = scale_boxes(img.shape[2:], pred[:, :4], orig_img.shape)
+        results.append(Results(orig_img, path=img_path, names=names, boxes=pred))
+
+    return results
+
+
+def empty_like(x):
+    return (
+        torch.empty_like(x, dtype=torch.float32) if isinstance(x, torch.Tensor) else np.empty_like(x, dtype=np.float32)
+    )
+
+
+def xywh2xyxy(x):
+    assert x.shape[-1] == 4, f"input shape last dimension expected 4 but input shape is {x.shape}"
+    y = empty_like(x)
+    xy = x[..., :2]
+    wh = x[..., 2:] / 2
+    y[..., :2] = xy - wh
+    y[..., 2:] = xy + wh
+    return y
+
+
+def non_max_suppression(
+    prediction,
+    conf_thres=0.25,
+    iou_thres=0.45,
+    classes=None,
+    agnostic=False,
+    multi_label=False,
+    labels=(),
+    max_det=300,
+    nc=0,
+    max_time_img=0.05,
+    max_nms=30000,
+    max_wh=7680,
+    in_place=True,
+    rotated=False,
+):
+    assert 0 <= conf_thres <= 1, f"Invalid Confidence threshold {conf_thres}, valid values are between 0.0 and 1.0"
+    assert 0 <= iou_thres <= 1, f"Invalid IoU {iou_thres}, valid values are between 0.0 and 1.0"
+
+    if isinstance(prediction, (list, tuple)):
+        prediction = prediction[0]
+    if classes is not None:
+        classes = torch.tensor(classes, device=prediction.device)
+
+    if prediction.shape[-1] == 6:
+        output = [pred[pred[:, 4] > conf_thres][:max_det] for pred in prediction]
+        if classes is not None:
+            output = [pred[(pred[:, 5:6] == classes).any(1)] for pred in output]
+        return output
+
+    bs = prediction.shape[0]
+    nc = nc or (prediction.shape[1] - 4)
+    nm = prediction.shape[1] - nc - 4
+    mi = 4 + nc
+    xc = prediction[:, 4:mi].amax(1) > conf_thres
path: models/experimental/functional_yolov9c/demo/demo_utils.py
user: login: kkeerthana0573
body: I've updated the model to use the existing `utils` from `models/experimental/yolo_evaluation/yolo_common_evaluation.py`
created_at: 2025-03-21T12:49:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,105 @@
+# SPDX-FileCopyrightText: © 2025 Tenstorrent Inc.
+
+# SPDX-License-Identifier: Apache-2.0
+
+import os
+import cv2
+import math
+import time
+import torch
+import requests
+import torchvision
+import numpy as np
+import torch.nn as nn
+from pathlib import Path
+from loguru import logger
+from datetime import datetime
+from functools import partial
+
+
+def imread(filename: str, flags: int = cv2.IMREAD_COLOR):
+    return cv2.imdecode(np.fromfile(filename, np.uint8), flags)
+
+
+class Ensemble(nn.ModuleList):
+    def __init__(self):
+        super(Ensemble, self).__init__()
+
+    def forward(self, x, augment=False):
+        y = []
+        for module in self:
+            y.append(module(x, augment)[0])
+        y = torch.cat(y, 1)
+        return y, None
+
+
+def attempt_download(file, repo="ultralytics/assets"):
+    tests = Path(__file__).parent.parent / "reference"
+    file_path = tests / Path(str(file).strip().replace("'", "").lower())
+
+    if not file_path.exists():
+        name = "yolov9c.pt"  # file_path.name
+        msg = f"{file_path} missing, try downloading from https://github.com/{repo}/releases/"
+
+        try:
+            url = f"https://github.com/{repo}/releases/download/v8.3.0/{name}"
+
+            print(f"Downloading {url} to {file_path}...")
+            torch.hub.download_url_to_file(url, file_path)
+
+            # Validate the file
+            assert file_path.exists() and file_path.stat().st_size > 1e6, f"Download failed for {name}"
+
+        except Exception as e:
+            print(f"Error downloading from GitHub: {e}. Trying secondary source...")
path: models/experimental/functional_yolov9c/demo/demo_utils.py
user: login: TT-BrianLiu
body: Consider using logger?
created_at: 2025-03-23T21:24:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,709 @@
+# SPDX-FileCopyrightText: © 2025 Tenstorrent Inc.
+
+# SPDX-License-Identifier: Apache-2.0
+
+import math
+import ttnn
+import torch
+from tt_lib.utils import _nearest_y
+from tests.ttnn.ttnn_utility_fuction import get_shard_grid_from_num_cores
+
+
+def to_layout(x, layout=ttnn.ROW_MAJOR_LAYOUT):
+    if x.get_layout() != layout:
path: models/experimental/functional_yolov9c/tt/ttnn_yolov9c.py
user: login: esmalTT
body: I don't think this is required, since to_layout does this check.
created_at: 2025-03-23T23:06:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,709 @@
+# SPDX-FileCopyrightText: © 2025 Tenstorrent Inc.
+
+# SPDX-License-Identifier: Apache-2.0
+
+import math
+import ttnn
+import torch
+from tt_lib.utils import _nearest_y
+from tests.ttnn.ttnn_utility_fuction import get_shard_grid_from_num_cores
+
+
+def to_layout(x, layout=ttnn.ROW_MAJOR_LAYOUT):
+    if x.get_layout() != layout:
+        x = ttnn.to_layout(x, layout)
+    return x
+
+
+def determine_num_cores(nhw: int, width: int, max_cores=64) -> int:
path: models/experimental/functional_yolov9c/tt/ttnn_yolov9c.py
user: login: esmalTT
body: This is copy and pasted from somewhere, we should avoid that.
created_at: 2025-03-23T23:06:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,709 @@
+# SPDX-FileCopyrightText: © 2025 Tenstorrent Inc.
+
+# SPDX-License-Identifier: Apache-2.0
+
+import math
+import ttnn
+import torch
+from tt_lib.utils import _nearest_y
+from tests.ttnn.ttnn_utility_fuction import get_shard_grid_from_num_cores
+
+
+def to_layout(x, layout=ttnn.ROW_MAJOR_LAYOUT):
+    if x.get_layout() != layout:
+        x = ttnn.to_layout(x, layout)
+    return x
+
+
+def determine_num_cores(nhw: int, width: int, max_cores=64) -> int:
+    gcd_nhw_width = math.gcd(nhw, width)
+    cores = nhw // gcd_nhw_width
+    if cores > max_cores:
+        for divisor in range(max_cores, 0, -1):
+            if nhw % divisor == 0 and (nhw // divisor) % width == 0:
+                cores = divisor
+                break
+    return cores
+
+
+def get_core_grid_from_num_cores(num_cores: int, grid_rows: int = 8, grid_cols: int = 8):
path: models/experimental/functional_yolov9c/tt/ttnn_yolov9c.py
user: login: esmalTT
body: This is also copy and pasted from somewhere, maybe it should go in a common place.
created_at: 2025-03-23T23:07:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,709 @@
+# SPDX-FileCopyrightText: © 2025 Tenstorrent Inc.
+
+# SPDX-License-Identifier: Apache-2.0
+
+import math
+import ttnn
+import torch
+from tt_lib.utils import _nearest_y
+from tests.ttnn.ttnn_utility_fuction import get_shard_grid_from_num_cores
+
+
+def to_layout(x, layout=ttnn.ROW_MAJOR_LAYOUT):
+    if x.get_layout() != layout:
+        x = ttnn.to_layout(x, layout)
+    return x
+
+
+def determine_num_cores(nhw: int, width: int, max_cores=64) -> int:
+    gcd_nhw_width = math.gcd(nhw, width)
+    cores = nhw // gcd_nhw_width
+    if cores > max_cores:
+        for divisor in range(max_cores, 0, -1):
+            if nhw % divisor == 0 and (nhw // divisor) % width == 0:
+                cores = divisor
+                break
+    return cores
+
+
+def get_core_grid_from_num_cores(num_cores: int, grid_rows: int = 8, grid_cols: int = 8):
+    rows = num_cores // grid_cols
+    assert rows <= grid_rows, "Not enough cores for specified core grid"
+    ranges = []
+    if rows != 0:
+        ranges.append(
+            ttnn.CoreRange(
+                ttnn.CoreCoord(0, 0),
+                ttnn.CoreCoord(grid_rows - 1, rows - 1),
+            )
+        )
+    remainder = num_cores % grid_rows
+    if remainder != 0:
+        assert rows + 1 <= grid_rows, "Not enough cores for specified core grid"
+        ranges.append(
+            ttnn.CoreRange(
+                ttnn.CoreCoord(0, rows),
+                ttnn.CoreCoord(remainder - 1, rows),
+            )
+        )
+    return ttnn.CoreRangeSet({*ranges})
+
+
+def interleaved_to_sharded(x):
+    x = to_layout(x, layout=ttnn.ROW_MAJOR_LAYOUT)
+    nhw = x.shape[0] * x.shape[1] * x.shape[2]
+    num_cores = determine_num_cores(nhw, int(math.sqrt(x.shape[2])))
+    num_cores = 56 if num_cores == 1 else num_cores
+    core_grid = get_core_grid_from_num_cores(num_cores)
+    shardspec = ttnn.create_sharded_memory_config_(
+        x.shape, core_grid, ttnn.ShardStrategy.HEIGHT, orientation=ttnn.ShardOrientation.ROW_MAJOR
+    )
+
+    return ttnn.reshard(x, shardspec) if x.is_sharded() else ttnn.interleaved_to_sharded(x, shardspec)
+
+
+class Yolov9c_Conv2D:
path: models/experimental/functional_yolov9c/tt/ttnn_yolov9c.py
user: login: esmalTT
body: We should probably follow PEP8 for styling ([TitleCase](https://peps.python.org/pep-0008/#class-names))
created_at: 2025-03-23T23:09:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,66 @@
+# SPDX-FileCopyrightText: © 2025 Tenstorrent Inc.
+
+# SPDX-License-Identifier: Apache-2.0
+
+import os
+import sys
+import ttnn
+import torch
+import pickle
+import pytest
+import torch.nn as nn
+from models.utility_functions import run_for_wormhole_b0
+from tests.ttnn.utils_for_testing import assert_with_pcc
+from models.experimental.functional_yolov9c.tt.model_preprocessing import (
+    create_yolov9c_input_tensors,
+    create_yolov9c_model_parameters,
+    create_yolov9c_model_parameters_detect,
+)
+from models.experimental.functional_yolov9c.tt import ttnn_yolov9c
+from models.experimental.functional_yolov9c.reference import yolov9c
+from models.experimental.functional_yolov9c.demo.demo_utils import attempt_load
+
+try:
+    sys.modules["ultralytics"] = yolov9c
path: tests/ttnn/integration_tests/yolov9c/test_ttnn_yolov9c.py
user: login: esmalTT
body: This is very janky, why is it needed? 
created_at: 2025-03-23T23:10:35Z
author_association: CONTRIBUTOR
path: tests/ttnn/integration_tests/yolov9c/test_ttnn_yolov9c.py
user: login: esmalTT
body: Has this been added to CI? I don't see where it has been added.
created_at: 2025-03-23T23:12:20Z
author_association: CONTRIBUTOR
path: tests/ttnn/integration_tests/yolov9c/test_ttnn_yolov9c.py
user: login: kkeerthana0573
body: This test is in `integration_tests`, and is added to Nightly - We've the model passing link [Here](https://github.com/tenstorrent/tt-metal/actions/runs/13974887568/job/39126519399#step:8:3678).
created_at: 2025-03-24T05:07:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,66 @@
+# SPDX-FileCopyrightText: © 2025 Tenstorrent Inc.
+
+# SPDX-License-Identifier: Apache-2.0
+
+import os
+import sys
+import ttnn
+import torch
+import pickle
+import pytest
+import torch.nn as nn
+from models.utility_functions import run_for_wormhole_b0
+from tests.ttnn.utils_for_testing import assert_with_pcc
+from models.experimental.functional_yolov9c.tt.model_preprocessing import (
+    create_yolov9c_input_tensors,
+    create_yolov9c_model_parameters,
+    create_yolov9c_model_parameters_detect,
+)
+from models.experimental.functional_yolov9c.tt import ttnn_yolov9c
+from models.experimental.functional_yolov9c.reference import yolov9c
+from models.experimental.functional_yolov9c.demo.demo_utils import attempt_load
+
+try:
+    sys.modules["ultralytics"] = yolov9c
path: tests/ttnn/integration_tests/yolov9c/test_ttnn_yolov9c.py
user: login: kkeerthana0573
body: To resolve Ultralytics dependency issue, this workaround has been implemented by manually mapping ultralytics modules to yolov9c.
created_at: 2025-03-24T18:13:49Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,105 @@
+# SPDX-FileCopyrightText: © 2025 Tenstorrent Inc.
+
+# SPDX-License-Identifier: Apache-2.0
+
+import os
+import cv2
+import math
+import time
+import torch
+import requests
+import torchvision
+import numpy as np
+import torch.nn as nn
+from pathlib import Path
+from loguru import logger
+from datetime import datetime
+from functools import partial
+
+
+def imread(filename: str, flags: int = cv2.IMREAD_COLOR):
+    return cv2.imdecode(np.fromfile(filename, np.uint8), flags)
+
+
+class Ensemble(nn.ModuleList):
+    def __init__(self):
+        super(Ensemble, self).__init__()
+
+    def forward(self, x, augment=False):
+        y = []
+        for module in self:
+            y.append(module(x, augment)[0])
+        y = torch.cat(y, 1)
+        return y, None
+
+
+def attempt_download(file, repo="ultralytics/assets"):
+    tests = Path(__file__).parent.parent / "reference"
+    file_path = tests / Path(str(file).strip().replace("'", "").lower())
+
+    if not file_path.exists():
+        name = "yolov9c.pt"  # file_path.name
+        msg = f"{file_path} missing, try downloading from https://github.com/{repo}/releases/"
+
+        try:
+            url = f"https://github.com/{repo}/releases/download/v8.3.0/{name}"
+
+            print(f"Downloading {url} to {file_path}...")
+            torch.hub.download_url_to_file(url, file_path)
+
+            # Validate the file
+            assert file_path.exists() and file_path.stat().st_size > 1e6, f"Download failed for {name}"
+
+        except Exception as e:
+            print(f"Error downloading from GitHub: {e}. Trying secondary source...")
path: models/experimental/functional_yolov9c/demo/demo_utils.py
user: login: kkeerthana0573
body: I've used logger accordingly.
Thank you.
created_at: 2025-03-24T18:21:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,709 @@
+# SPDX-FileCopyrightText: © 2025 Tenstorrent Inc.
+
+# SPDX-License-Identifier: Apache-2.0
+
+import math
+import ttnn
+import torch
+from tt_lib.utils import _nearest_y
+from tests.ttnn.ttnn_utility_fuction import get_shard_grid_from_num_cores
+
+
+def to_layout(x, layout=ttnn.ROW_MAJOR_LAYOUT):
+    if x.get_layout() != layout:
path: models/experimental/functional_yolov9c/tt/ttnn_yolov9c.py
user: login: kkeerthana0573
body: I've updated the model to directly use ttnn.to_layout instead of the above check. 
created_at: 2025-03-24T18:21:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,709 @@
+# SPDX-FileCopyrightText: © 2025 Tenstorrent Inc.
+
+# SPDX-License-Identifier: Apache-2.0
+
+import math
+import ttnn
+import torch
+from tt_lib.utils import _nearest_y
+from tests.ttnn.ttnn_utility_fuction import get_shard_grid_from_num_cores
+
+
+def to_layout(x, layout=ttnn.ROW_MAJOR_LAYOUT):
+    if x.get_layout() != layout:
+        x = ttnn.to_layout(x, layout)
+    return x
+
+
+def determine_num_cores(nhw: int, width: int, max_cores=64) -> int:
path: models/experimental/functional_yolov9c/tt/ttnn_yolov9c.py
user: login: kkeerthana0573
body: I've placed them in  `models/experimental/yolo_common/yolo_utils.py` [here](https://github.com/tenstorrent/tt-metal/blob/d2d1edda1c910f956cc1ad49ba86658d79a417c1/models/experimental/yolo_common/yolo_utils.py).
created_at: 2025-03-24T18:22:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,709 @@
+# SPDX-FileCopyrightText: © 2025 Tenstorrent Inc.
+
+# SPDX-License-Identifier: Apache-2.0
+
+import math
+import ttnn
+import torch
+from tt_lib.utils import _nearest_y
+from tests.ttnn.ttnn_utility_fuction import get_shard_grid_from_num_cores
+
+
+def to_layout(x, layout=ttnn.ROW_MAJOR_LAYOUT):
+    if x.get_layout() != layout:
+        x = ttnn.to_layout(x, layout)
+    return x
+
+
+def determine_num_cores(nhw: int, width: int, max_cores=64) -> int:
+    gcd_nhw_width = math.gcd(nhw, width)
+    cores = nhw // gcd_nhw_width
+    if cores > max_cores:
+        for divisor in range(max_cores, 0, -1):
+            if nhw % divisor == 0 and (nhw // divisor) % width == 0:
+                cores = divisor
+                break
+    return cores
+
+
+def get_core_grid_from_num_cores(num_cores: int, grid_rows: int = 8, grid_cols: int = 8):
path: models/experimental/functional_yolov9c/tt/ttnn_yolov9c.py
user: login: kkeerthana0573
body: I've placed them in  `models/experimental/yolo_common/yolo_utils.py` [here](https://github.com/tenstorrent/tt-metal/blob/d2d1edda1c910f956cc1ad49ba86658d79a417c1/models/experimental/yolo_common/yolo_utils.py).
created_at: 2025-03-24T18:23:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,709 @@
+# SPDX-FileCopyrightText: © 2025 Tenstorrent Inc.
+
+# SPDX-License-Identifier: Apache-2.0
+
+import math
+import ttnn
+import torch
+from tt_lib.utils import _nearest_y
+from tests.ttnn.ttnn_utility_fuction import get_shard_grid_from_num_cores
+
+
+def to_layout(x, layout=ttnn.ROW_MAJOR_LAYOUT):
+    if x.get_layout() != layout:
+        x = ttnn.to_layout(x, layout)
+    return x
+
+
+def determine_num_cores(nhw: int, width: int, max_cores=64) -> int:
+    gcd_nhw_width = math.gcd(nhw, width)
+    cores = nhw // gcd_nhw_width
+    if cores > max_cores:
+        for divisor in range(max_cores, 0, -1):
+            if nhw % divisor == 0 and (nhw // divisor) % width == 0:
+                cores = divisor
+                break
+    return cores
+
+
+def get_core_grid_from_num_cores(num_cores: int, grid_rows: int = 8, grid_cols: int = 8):
+    rows = num_cores // grid_cols
+    assert rows <= grid_rows, "Not enough cores for specified core grid"
+    ranges = []
+    if rows != 0:
+        ranges.append(
+            ttnn.CoreRange(
+                ttnn.CoreCoord(0, 0),
+                ttnn.CoreCoord(grid_rows - 1, rows - 1),
+            )
+        )
+    remainder = num_cores % grid_rows
+    if remainder != 0:
+        assert rows + 1 <= grid_rows, "Not enough cores for specified core grid"
+        ranges.append(
+            ttnn.CoreRange(
+                ttnn.CoreCoord(0, rows),
+                ttnn.CoreCoord(remainder - 1, rows),
+            )
+        )
+    return ttnn.CoreRangeSet({*ranges})
+
+
+def interleaved_to_sharded(x):
+    x = to_layout(x, layout=ttnn.ROW_MAJOR_LAYOUT)
+    nhw = x.shape[0] * x.shape[1] * x.shape[2]
+    num_cores = determine_num_cores(nhw, int(math.sqrt(x.shape[2])))
+    num_cores = 56 if num_cores == 1 else num_cores
+    core_grid = get_core_grid_from_num_cores(num_cores)
+    shardspec = ttnn.create_sharded_memory_config_(
+        x.shape, core_grid, ttnn.ShardStrategy.HEIGHT, orientation=ttnn.ShardOrientation.ROW_MAJOR
+    )
+
+    return ttnn.reshard(x, shardspec) if x.is_sharded() else ttnn.interleaved_to_sharded(x, shardspec)
+
+
+class Yolov9c_Conv2D:
path: models/experimental/functional_yolov9c/tt/ttnn_yolov9c.py
user: login: kkeerthana0573
body: I've updated.
created_at: 2025-03-24T18:23:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,671 @@
+# SPDX-FileCopyrightText: © 2025 Tenstorrent Inc.
+
+# SPDX-License-Identifier: Apache-2.0
+
+import math
+import ttnn
+import torch
+from tt_lib.utils import _nearest_y
+from models.experimental.yolo_common.yolo_utils import determine_num_cores, get_core_grid_from_num_cores
+
+
+def interleaved_to_sharded(x):
+    x = ttnn.to_layout(x, layout=ttnn.ROW_MAJOR_LAYOUT)
+    nhw = x.shape[0] * x.shape[1] * x.shape[2]
+    num_cores = determine_num_cores(nhw, int(math.sqrt(x.shape[2])))
+    num_cores = 56 if num_cores == 1 else num_cores
+    core_grid = get_core_grid_from_num_cores(num_cores)
+    shardspec = ttnn.create_sharded_memory_config_(
+        x.shape, core_grid, ttnn.ShardStrategy.HEIGHT, orientation=ttnn.ShardOrientation.ROW_MAJOR
+    )
+
+    return ttnn.reshard(x, shardspec) if x.is_sharded() else ttnn.interleaved_to_sharded(x, shardspec)
+
+
+class yolov9c_conv2D:
path: models/experimental/functional_yolov9c/tt/ttnn_yolov9c.py
user: login: esmalTT
body: This isn't title case...
created_at: 2025-03-24T22:24:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,671 @@
+# SPDX-FileCopyrightText: © 2025 Tenstorrent Inc.
+
+# SPDX-License-Identifier: Apache-2.0
+
+import math
+import ttnn
+import torch
+from tt_lib.utils import _nearest_y
+from models.experimental.yolo_common.yolo_utils import determine_num_cores, get_core_grid_from_num_cores
+
+
+def interleaved_to_sharded(x):
+    x = ttnn.to_layout(x, layout=ttnn.ROW_MAJOR_LAYOUT)
+    nhw = x.shape[0] * x.shape[1] * x.shape[2]
+    num_cores = determine_num_cores(nhw, int(math.sqrt(x.shape[2])))
+    num_cores = 56 if num_cores == 1 else num_cores
+    core_grid = get_core_grid_from_num_cores(num_cores)
+    shardspec = ttnn.create_sharded_memory_config_(
+        x.shape, core_grid, ttnn.ShardStrategy.HEIGHT, orientation=ttnn.ShardOrientation.ROW_MAJOR
+    )
+
+    return ttnn.reshard(x, shardspec) if x.is_sharded() else ttnn.interleaved_to_sharded(x, shardspec)
+
+
+class yolov9c_conv2D:
+    def __init__(
+        self,
+        conv,
+        conv_pth,
+        bn=None,
+        device=None,
+        cache={},
+        activation="",
+        activation_dtype=ttnn.bfloat8_b,
+        weights_dtype=ttnn.bfloat8_b,
+        use_1d_systolic_array=True,
+        shard_layout=ttnn.TensorMemoryLayout.HEIGHT_SHARDED,
+        is_detect=False,
+        is_dfl=False,
+        config_override=None,
+    ):
+        self.is_detect = is_detect
+        self.is_dfl = is_dfl
+        self.conv = conv
+        self.device = device
+        self.in_channels = conv.in_channels
+        self.out_channels = conv.out_channels
+        self.kernel_size = conv.kernel_size
+        self.padding = conv.padding
+        self.stride = conv.stride
+        self.groups = conv.groups
+        self.use_1d_systolic_array = use_1d_systolic_array
+        self.deallocate_activation = False
+        self.cache = cache
+        self.compute_config = ttnn.init_device_compute_kernel_config(
+            device.arch(),
+            math_fidelity=ttnn.MathFidelity.LoFi,
+            fp32_dest_acc_en=False,
+            packer_l1_acc=False,
+            math_approx_mode=False,
+        )
+        self.conv_config = ttnn.Conv2dConfig(
+            dtype=activation_dtype,
+            weights_dtype=weights_dtype,
+            shard_layout=shard_layout,
+            deallocate_activation=self.deallocate_activation,
+            enable_act_double_buffer=False,
+            enable_split_reader=False,
+            enable_subblock_padding=False,
+            reshard_if_not_optimal=True if self.use_1d_systolic_array else False,
+            activation=activation,
+            input_channels_alignment=32,
+        )
+        config_override = {"act_block_h": 64} if conv.in_channels == 3 else None
+        if config_override and "act_block_h" in config_override:
+            self.conv_config.act_block_h_override = config_override["act_block_h"]
+
+        if "bias" in conv_pth:
+            bias = ttnn.from_device(conv_pth.bias)
+            self.bias = bias
+        else:
+            self.bias = None
+
+        weight = ttnn.from_device(conv_pth.weight)
+        self.weight = weight
+
+    def __call__(self, x):
+        if self.is_detect:
+            input_height = int(math.sqrt(x.shape[2]))
+            input_width = int(math.sqrt(x.shape[2]))
+            batch_size = x.shape[0]
+        elif self.is_dfl:
+            input_height = x.shape[1]
+            input_width = x.shape[2]
+            batch_size = x.shape[0]
+        else:
+            batch_size = self.conv.batch_size
+            input_height = self.conv.input_height
+            input_width = self.conv.input_width
+
+        [x, [output_height, output_width], [self.weight, self.bias]] = ttnn.conv2d(
+            input_tensor=x,
+            weight_tensor=self.weight,
+            bias_tensor=self.bias,
+            device=self.device,
+            in_channels=self.in_channels,
+            out_channels=self.out_channels,
+            input_height=input_height,
+            input_width=input_width,
+            batch_size=batch_size,
+            kernel_size=self.kernel_size,
+            stride=self.stride,
+            padding=self.padding,
+            conv_config=self.conv_config,
+            conv_op_cache=self.cache,
+            groups=self.groups,
+            compute_config=self.compute_config,
+            return_output_dim=True,
+            return_weights_and_bias=True,
+        )
+        hw = output_height * output_width
+        if x.shape[2] != hw:
+            x = x[:, :, :hw, :]
+        return x
+
+
+def sharded_concat(input_tensors, num_cores=56, dim=3):  # expected input tensors to be in fp16, RM, same (h*w)
+    shard_grid = get_core_grid_from_num_cores(num_cores=num_cores)
+    in_shard_width = input_tensors[0].shape[-1]
+    shard_height = (input_tensors[0].shape[2] + num_cores - 1) // num_cores
+    input_sharded_memory_config = ttnn.create_sharded_memory_config_(
+        (shard_height, in_shard_width),
+        core_grid=shard_grid,
+        strategy=ttnn.ShardStrategy.HEIGHT,
+        orientation=ttnn.ShardOrientation.ROW_MAJOR,
+        use_height_and_width_as_shard_shape=True,
+    )
+    out_shard_width = 0
+    for i in range(len(input_tensors)):
+        out_shard_width += input_tensors[i].shape[-1]
+        input_tensors[i] = ttnn.to_memory_config(input_tensors[i], input_sharded_memory_config)
+
+    output_sharded_memory_config = ttnn.create_sharded_memory_config_(
+        (shard_height, out_shard_width),
+        core_grid=shard_grid,
+        strategy=ttnn.ShardStrategy.HEIGHT,
+        orientation=ttnn.ShardOrientation.ROW_MAJOR,
+        use_height_and_width_as_shard_shape=True,
+    )
+    output = ttnn.concat(input_tensors, dim, memory_config=output_sharded_memory_config)
+    return output
+
+
+def concat(dim=-1, use_sharded_concat=True, *tensors):
+    if use_sharded_concat:
+        processed_tensors = [
+            ttnn.to_dtype(ttnn.to_layout(tensor, ttnn.ROW_MAJOR_LAYOUT), ttnn.bfloat16) for tensor in tensors
+        ]
+        return sharded_concat(processed_tensors)
+    else:
+        processed_tensors = [
+            ttnn.sharded_to_interleaved(tensor) if tensor.is_sharded() else tensor for tensor in tensors
+        ]
+        return ttnn.concat(processed_tensors, dim=dim, memory_config=ttnn.L1_MEMORY_CONFIG)
+
+
+class ttnn_repconv:
+    def __init__(self, device, parameter, conv_pt):
+        self.conv1 = yolov9c_conv2D(
+            device=device, conv=parameter.conv1.conv, conv_pth=conv_pt.conv1.conv, activation=""
+        )
+        self.conv2 = yolov9c_conv2D(
+            device=device, conv=parameter.conv2.conv, conv_pth=conv_pt.conv2.conv, activation=""
+        )
+
+    def __call__(self, device, x):
+        conv1_out = self.conv1(x)
+        conv2_out = self.conv2(x)
+        x = ttnn.silu(conv1_out + conv2_out)
+
+        return x
+
+
+class ttnn_repbottleneck:
path: models/experimental/functional_yolov9c/tt/ttnn_yolov9c.py
user: login: esmalTT
body: same here... please look at the PEP8 documentation
created_at: 2025-03-24T22:24:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,671 @@
+# SPDX-FileCopyrightText: © 2025 Tenstorrent Inc.
+
+# SPDX-License-Identifier: Apache-2.0
+
+import math
+import ttnn
+import torch
+from tt_lib.utils import _nearest_y
+from models.experimental.yolo_common.yolo_utils import determine_num_cores, get_core_grid_from_num_cores
+
+
+def interleaved_to_sharded(x):
+    x = ttnn.to_layout(x, layout=ttnn.ROW_MAJOR_LAYOUT)
+    nhw = x.shape[0] * x.shape[1] * x.shape[2]
+    num_cores = determine_num_cores(nhw, int(math.sqrt(x.shape[2])))
+    num_cores = 56 if num_cores == 1 else num_cores
+    core_grid = get_core_grid_from_num_cores(num_cores)
+    shardspec = ttnn.create_sharded_memory_config_(
+        x.shape, core_grid, ttnn.ShardStrategy.HEIGHT, orientation=ttnn.ShardOrientation.ROW_MAJOR
+    )
+
+    return ttnn.reshard(x, shardspec) if x.is_sharded() else ttnn.interleaved_to_sharded(x, shardspec)
+
+
+class yolov9c_conv2D:
path: models/experimental/functional_yolov9c/tt/ttnn_yolov9c.py
user: login: kkeerthana0573
body: The model is updated with PEP8 Tile Case.
created_at: 2025-03-25T09:49:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,671 @@
+# SPDX-FileCopyrightText: © 2025 Tenstorrent Inc.
+
+# SPDX-License-Identifier: Apache-2.0
+
+import math
+import ttnn
+import torch
+from tt_lib.utils import _nearest_y
+from models.experimental.yolo_common.yolo_utils import determine_num_cores, get_core_grid_from_num_cores
+
+
+def interleaved_to_sharded(x):
+    x = ttnn.to_layout(x, layout=ttnn.ROW_MAJOR_LAYOUT)
+    nhw = x.shape[0] * x.shape[1] * x.shape[2]
+    num_cores = determine_num_cores(nhw, int(math.sqrt(x.shape[2])))
+    num_cores = 56 if num_cores == 1 else num_cores
+    core_grid = get_core_grid_from_num_cores(num_cores)
+    shardspec = ttnn.create_sharded_memory_config_(
+        x.shape, core_grid, ttnn.ShardStrategy.HEIGHT, orientation=ttnn.ShardOrientation.ROW_MAJOR
+    )
+
+    return ttnn.reshard(x, shardspec) if x.is_sharded() else ttnn.interleaved_to_sharded(x, shardspec)
+
+
+class yolov9c_conv2D:
+    def __init__(
+        self,
+        conv,
+        conv_pth,
+        bn=None,
+        device=None,
+        cache={},
+        activation="",
+        activation_dtype=ttnn.bfloat8_b,
+        weights_dtype=ttnn.bfloat8_b,
+        use_1d_systolic_array=True,
+        shard_layout=ttnn.TensorMemoryLayout.HEIGHT_SHARDED,
+        is_detect=False,
+        is_dfl=False,
+        config_override=None,
+    ):
+        self.is_detect = is_detect
+        self.is_dfl = is_dfl
+        self.conv = conv
+        self.device = device
+        self.in_channels = conv.in_channels
+        self.out_channels = conv.out_channels
+        self.kernel_size = conv.kernel_size
+        self.padding = conv.padding
+        self.stride = conv.stride
+        self.groups = conv.groups
+        self.use_1d_systolic_array = use_1d_systolic_array
+        self.deallocate_activation = False
+        self.cache = cache
+        self.compute_config = ttnn.init_device_compute_kernel_config(
+            device.arch(),
+            math_fidelity=ttnn.MathFidelity.LoFi,
+            fp32_dest_acc_en=False,
+            packer_l1_acc=False,
+            math_approx_mode=False,
+        )
+        self.conv_config = ttnn.Conv2dConfig(
+            dtype=activation_dtype,
+            weights_dtype=weights_dtype,
+            shard_layout=shard_layout,
+            deallocate_activation=self.deallocate_activation,
+            enable_act_double_buffer=False,
+            enable_split_reader=False,
+            enable_subblock_padding=False,
+            reshard_if_not_optimal=True if self.use_1d_systolic_array else False,
+            activation=activation,
+            input_channels_alignment=32,
+        )
+        config_override = {"act_block_h": 64} if conv.in_channels == 3 else None
+        if config_override and "act_block_h" in config_override:
+            self.conv_config.act_block_h_override = config_override["act_block_h"]
+
+        if "bias" in conv_pth:
+            bias = ttnn.from_device(conv_pth.bias)
+            self.bias = bias
+        else:
+            self.bias = None
+
+        weight = ttnn.from_device(conv_pth.weight)
+        self.weight = weight
+
+    def __call__(self, x):
+        if self.is_detect:
+            input_height = int(math.sqrt(x.shape[2]))
+            input_width = int(math.sqrt(x.shape[2]))
+            batch_size = x.shape[0]
+        elif self.is_dfl:
+            input_height = x.shape[1]
+            input_width = x.shape[2]
+            batch_size = x.shape[0]
+        else:
+            batch_size = self.conv.batch_size
+            input_height = self.conv.input_height
+            input_width = self.conv.input_width
+
+        [x, [output_height, output_width], [self.weight, self.bias]] = ttnn.conv2d(
+            input_tensor=x,
+            weight_tensor=self.weight,
+            bias_tensor=self.bias,
+            device=self.device,
+            in_channels=self.in_channels,
+            out_channels=self.out_channels,
+            input_height=input_height,
+            input_width=input_width,
+            batch_size=batch_size,
+            kernel_size=self.kernel_size,
+            stride=self.stride,
+            padding=self.padding,
+            conv_config=self.conv_config,
+            conv_op_cache=self.cache,
+            groups=self.groups,
+            compute_config=self.compute_config,
+            return_output_dim=True,
+            return_weights_and_bias=True,
+        )
+        hw = output_height * output_width
+        if x.shape[2] != hw:
+            x = x[:, :, :hw, :]
+        return x
+
+
+def sharded_concat(input_tensors, num_cores=56, dim=3):  # expected input tensors to be in fp16, RM, same (h*w)
+    shard_grid = get_core_grid_from_num_cores(num_cores=num_cores)
+    in_shard_width = input_tensors[0].shape[-1]
+    shard_height = (input_tensors[0].shape[2] + num_cores - 1) // num_cores
+    input_sharded_memory_config = ttnn.create_sharded_memory_config_(
+        (shard_height, in_shard_width),
+        core_grid=shard_grid,
+        strategy=ttnn.ShardStrategy.HEIGHT,
+        orientation=ttnn.ShardOrientation.ROW_MAJOR,
+        use_height_and_width_as_shard_shape=True,
+    )
+    out_shard_width = 0
+    for i in range(len(input_tensors)):
+        out_shard_width += input_tensors[i].shape[-1]
+        input_tensors[i] = ttnn.to_memory_config(input_tensors[i], input_sharded_memory_config)
+
+    output_sharded_memory_config = ttnn.create_sharded_memory_config_(
+        (shard_height, out_shard_width),
+        core_grid=shard_grid,
+        strategy=ttnn.ShardStrategy.HEIGHT,
+        orientation=ttnn.ShardOrientation.ROW_MAJOR,
+        use_height_and_width_as_shard_shape=True,
+    )
+    output = ttnn.concat(input_tensors, dim, memory_config=output_sharded_memory_config)
+    return output
+
+
+def concat(dim=-1, use_sharded_concat=True, *tensors):
+    if use_sharded_concat:
+        processed_tensors = [
+            ttnn.to_dtype(ttnn.to_layout(tensor, ttnn.ROW_MAJOR_LAYOUT), ttnn.bfloat16) for tensor in tensors
+        ]
+        return sharded_concat(processed_tensors)
+    else:
+        processed_tensors = [
+            ttnn.sharded_to_interleaved(tensor) if tensor.is_sharded() else tensor for tensor in tensors
+        ]
+        return ttnn.concat(processed_tensors, dim=dim, memory_config=ttnn.L1_MEMORY_CONFIG)
+
+
+class ttnn_repconv:
+    def __init__(self, device, parameter, conv_pt):
+        self.conv1 = yolov9c_conv2D(
+            device=device, conv=parameter.conv1.conv, conv_pth=conv_pt.conv1.conv, activation=""
+        )
+        self.conv2 = yolov9c_conv2D(
+            device=device, conv=parameter.conv2.conv, conv_pth=conv_pt.conv2.conv, activation=""
+        )
+
+    def __call__(self, device, x):
+        conv1_out = self.conv1(x)
+        conv2_out = self.conv2(x)
+        x = ttnn.silu(conv1_out + conv2_out)
+
+        return x
+
+
+class ttnn_repbottleneck:
path: models/experimental/functional_yolov9c/tt/ttnn_yolov9c.py
user: login: kkeerthana0573
body: The model is updated with PEP8 Tile Case.
created_at: 2025-03-25T09:49:16Z
author_association: CONTRIBUTOR

PR comments:
user: login: kkeerthana0573
created_at: 2025-03-20T19:45:36Z
body: Passing CI links:
[All post-commit tests](https://github.com/tenstorrent/tt-metal/actions/runs/13974882490)
[(Single-card) Nightly model and ttnn tests](https://github.com/tenstorrent/tt-metal/actions/runs/13974887568)
author_association: CONTRIBUTOR
user: login: ssinghalTT
created_at: 2025-03-20T21:56:02Z
body: Also ran:
[(Single-card) Tests for new models](https://github.com/tenstorrent/tt-metal/actions/runs/13974891567)
author_association: CONTRIBUTOR
user: login: kkeerthana0573
created_at: 2025-03-25T11:47:58Z
body: Passing CI links: 

- [All post-commit tests](https://github.com/tenstorrent/tt-metal/actions/runs/14056618194) 
- [(Single-card) Nightly model and ttnn tests](https://github.com/tenstorrent/tt-metal/actions/runs/14056624196)
  - [fd-nightly / FD WH N150 ttnn nightly wormhole_b0](https://github.com/tenstorrent/tt-metal/actions/runs/14056624196/job/39357742463#step:8:3516)
  - [fd-nightly / FD WH N300 ttnn nightly wormhole_b0](https://github.com/tenstorrent/tt-metal/actions/runs/14056624196/job/39357743635#step:8:3512)
author_association: CONTRIBUTOR