Skip to content

Commit

Permalink
Add export output names specification for individual nodes (#112)
Browse files Browse the repository at this point in the history
Co-authored-by: KlemenSkrlj <[email protected]>
  • Loading branch information
sokovninn and klemen1999 authored Oct 15, 2024
1 parent 6146da6 commit 72217f1
Show file tree
Hide file tree
Showing 9 changed files with 108 additions and 56 deletions.
3 changes: 1 addition & 2 deletions configs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,7 @@ Here you can define configuration for exporting.
| `mean_values` | `list[float] \| None` | `None` | What mean values to use for input normalization. If not provided, inferred from augmentations |
| `upload_to_run` | `bool` | `True` | Whether to upload the exported files to tracked run as artifact |
| `upload_url` | `str \| None` | `None` | Exported model will be uploaded to this URL if specified |
| `output_names` | `list[str] \| None` | `None` | Optional list of output names to override the default ones |
| `output_names` | `list[str] \| None` | `None` | Optional list of output names to override the default ones (deprecated) |

### `ONNX`

Expand All @@ -411,7 +411,6 @@ Option specific for `ONNX` export.

```yaml
exporter:
output_names: ["output1", "output2"]
onnx:
opset_version: 11
blobconverter:
Expand Down
3 changes: 2 additions & 1 deletion configs/example_export.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ model:
name: DetectionModel
params:
variant: light
head_params:
export_output_names: [output1_yolov6r2, output2_yolov6r2, output3_yolov6r2]

loader:
params:
Expand Down Expand Up @@ -42,7 +44,6 @@ trainer:
last_epoch: -1

exporter:
output_names: [output1_yolov6r2, output2_yolov6r2, output3_yolov6r2]
onnx:
opset_version: 11
blobconverter:
Expand Down
33 changes: 10 additions & 23 deletions luxonis_train/core/utils/archive_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,26 +186,7 @@ def _get_head_outputs(
if name == head_name:
output_names.append(output["name"])

if output_names:
return output_names

# TODO: Fix this, will require refactoring custom ONNX output names
logger.error(
"ONNX model uses custom output names, trying to determine outputs based on the head type. "
"This will likely result in incorrect archive for multi-head models. "
"You can ignore this error if your model has only one head."
)

if head_type == "ClassificationHead":
return [outputs[0]["name"]]
elif head_type == "EfficientBBoxHead":
return [output["name"] for output in outputs]
elif head_type in ["SegmentationHead", "BiSeNetHead"]:
return [outputs[0]["name"]]
elif head_type == "EfficientKeypointBBoxHead":
return [outputs[0]["name"]]
else:
raise ValueError("Unknown head name")
return output_names


def get_heads(
Expand Down Expand Up @@ -238,9 +219,15 @@ def get_heads(
task = str(next(iter(task.values())))

classes = _get_classes(node_name, task, class_dict)
head_outputs = _get_head_outputs(
outputs, node_alias, node_name
)

export_output_names = nodes[node_alias].export_output_names
if export_output_names is not None:
head_outputs = export_output_names
else:
head_outputs = _get_head_outputs(
outputs, node_alias, node_name
)

if node_alias in head_names:
curr_head_name = f"{node_alias}_{len(head_names)}" # add suffix if name is already present
else:
Expand Down
60 changes: 49 additions & 11 deletions luxonis_train/models/luxonis_lightning.py
Original file line number Diff line number Diff line change
Expand Up @@ -520,7 +520,38 @@ def export_onnx(self, save_path: str, **kwargs) -> list[str]:
]
)

output_counts = defaultdict(int)
for node_name, outs in outputs.items():
output_counts[node_name] = sum(len(out) for out in outs.values())

if self.cfg.exporter.output_names is not None:
logger.warning(
"The use of 'exporter.output_names' is deprecated and will be removed in a future version. "
"If 'node.export_output_names' are provided, they will take precedence and overwrite 'exporter.output_names'. "
"Please update your config to use 'node.export_output_names' directly."
)

export_output_names_used = False
export_output_names_dict = {}
for node_name, node in self.nodes.items():
if node.export_output_names is not None:
export_output_names_used = True
if len(node.export_output_names) != output_counts[node_name]:
logger.warning(
f"Number of provided output names for node {node_name} "
f"({len(node.export_output_names)}) does not match "
f"number of outputs ({output_counts[node_name]}). "
f"Using default names."
)
else:
export_output_names_dict[node_name] = (
node.export_output_names
)

if (
not export_output_names_used
and self.cfg.exporter.output_names is not None
):
len_names = len(self.cfg.exporter.output_names)
if len_names != len(output_order):
logger.warning(
Expand All @@ -529,18 +560,25 @@ def export_onnx(self, save_path: str, **kwargs) -> list[str]:
)
self.cfg.exporter.output_names = None

output_names = self.cfg.exporter.output_names or [
f"{node_name}/{output_name}/{i}"
for node_name, output_name, i in output_order
]
output_names = self.cfg.exporter.output_names or [
f"{node_name}/{output_name}/{i}"
for node_name, output_name, i in output_order
]

if not self.cfg.exporter.output_names:
idx = 1
# Set to output names required by DAI
for i, output_name in enumerate(output_names):
if output_name.startswith("EfficientBBoxHead"):
output_names[i] = f"output{idx}_yolov6r2"
idx += 1
if not self.cfg.exporter.output_names:
idx = 1
# Set to output names required by DAI
for i, output_name in enumerate(output_names):
if output_name.startswith("EfficientBBoxHead"):
output_names[i] = f"output{idx}_yolov6r2"
idx += 1
else:
output_names = []
for node_name, output_name, i in output_order:
if node_name in export_output_names_dict:
output_names.append(export_output_names_dict[node_name][i])
else:
output_names.append(f"{node_name}/{output_name}/{i}")

old_forward = self.forward

Expand Down
12 changes: 12 additions & 0 deletions luxonis_train/nodes/base_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ def __init__(
n_keypoints: int | None = None,
in_sizes: Size | list[Size] | None = None,
remove_on_export: bool = False,
export_output_names: list[str] | None = None,
attach_index: AttachIndexType | None = None,
_tasks: dict[TaskType, str] | None = None,
):
Expand All @@ -139,6 +140,11 @@ def __init__(
@type in_sizes: Size | list[Size] | None
@param in_sizes: List of input sizes for the node. Provide only
in case the C{input_shapes} were not provided.
@type remove_on_export: bool
@param remove_on_export: If set to True, the node will be removed
from the model during export. Defaults to False.
@type export_output_names: list[str] | None
@param export_output_names: List of output names for the export.
@type attach_index: AttachIndexType
@param attach_index: Index of previous output that this node
attaches to. Can be a single integer to specify a single
Expand Down Expand Up @@ -186,6 +192,7 @@ def __init__(
self._n_keypoints = n_keypoints
self._export = False
self._remove_on_export = remove_on_export
self._export_output_names = export_output_names
self._epoch = 0
self._in_sizes = in_sizes

Expand Down Expand Up @@ -513,6 +520,11 @@ def remove_on_export(self) -> bool:
"""Getter for the remove_on_export attribute."""
return self._remove_on_export

@property
def export_output_names(self) -> list[str] | None:
"""Getter for the export_output_names attribute."""
return self._export_output_names

def unwrap(self, inputs: list[Packet[Tensor]]) -> ForwardInputT:
"""Prepares inputs for the forward pass.
Expand Down
16 changes: 16 additions & 0 deletions luxonis_train/nodes/heads/efficient_bbox_head.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,22 @@ def __init__(
in_channels=self.in_channels[i],
)
self.heads.append(curr_head)
if (
self.export_output_names is None
or len(self.export_output_names) != self.n_heads
):
if (
self.export_output_names is not None
and len(self.export_output_names) != self.n_heads
):
logger.warning(
f"Number of provided output names ({len(self.export_output_names)}) "
f"does not match number of heads ({self.n_heads}). "
f"Using default names."
)
self._export_output_names = [
f"output{i+1}_yolov6r2" for i in range(self.n_heads)
]

def forward(
self, inputs: list[Tensor]
Expand Down
2 changes: 2 additions & 0 deletions luxonis_train/nodes/heads/efficient_keypoint_bbox_head.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ def __init__(
for x in self.in_channels
)

self._export_output_names = None

def forward(
self, inputs: list[Tensor]
) -> tuple[list[Tensor], list[Tensor], list[Tensor], list[Tensor]]:
Expand Down
23 changes: 10 additions & 13 deletions tests/configs/archive_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,33 +7,30 @@ model:
- name: EfficientBBoxHead
inputs:
- EfficientRep
params:
export_output_names: [bbox0, bbox1, bbox2]

- name: EfficientKeypointBBoxHead
inputs:
- EfficientRep
params:
export_output_names: [effkpt0, effkpt1, effkpt2]

- name: SegmentationHead
inputs:
- EfficientRep
params:
export_output_names: [seg0, seg1]

- name: BiSeNetHead
inputs:
- EfficientRep
params:
export_output_names: [impl]

- name: ClassificationHead
inputs:
- EfficientRep

exporter:
output_names:
- seg0
- class0
- bbox0
- bbox1
- bbox2
- effkpt0
- effkpt1
- effkpt2
- impl
- seg1
params:
export_output_names: [class0]

12 changes: 6 additions & 6 deletions tests/integration/parking_lot.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
"layout": "NCHW"
},
{
"name": "bbox-head/boundingbox/0",
"name": "output1_yolov6r2",
"dtype": "float32",
"shape": [
1,
Expand All @@ -58,7 +58,7 @@
"layout": "NCHW"
},
{
"name": "bbox-head/boundingbox/1",
"name": "output2_yolov6r2",
"dtype": "float32",
"shape": [
1,
Expand All @@ -69,7 +69,7 @@
"layout": "NCHW"
},
{
"name": "bbox-head/boundingbox/2",
"name": "output3_yolov6r2",
"dtype": "float32",
"shape": [
1,
Expand Down Expand Up @@ -164,9 +164,9 @@
"subtype": "yolov6"
},
"outputs": [
"bbox-head/boundingbox/0",
"bbox-head/boundingbox/1",
"bbox-head/boundingbox/2"
"output1_yolov6r2",
"output2_yolov6r2",
"output3_yolov6r2"
]
},
{
Expand Down

0 comments on commit 72217f1

Please sign in to comment.