Skip to content

Commit

Permalink
Generate deprecation warning for MultiMessage (#1719)
Browse files Browse the repository at this point in the history
- Generate deprecation warning for any usage of `MultiMessage`
- Update documentation to illustrate conversion from `MultiMessage` to `ControlMessage`




Closes #1718 

## By Submitting this PR I confirm:
- I am familiar with the [Contributing Guidelines](https://github.com/nv-morpheus/Morpheus/blob/main/docs/source/developer_guide/contributing.md).
- When the PR is ready for review, new or existing tests cover these changes.
- When the PR is ready for review, the documentation is up to date with these changes.

Authors:
  - Yuchen Zhang (https://github.com/yczhang-nv)

Approvers:
  - Michael Demoret (https://github.com/mdemoret-nv)

URL: #1719
  • Loading branch information
yczhang-nv authored May 31, 2024
1 parent 12abdce commit 580be43
Show file tree
Hide file tree
Showing 7 changed files with 101 additions and 15 deletions.
21 changes: 21 additions & 0 deletions docs/source/developer_guide/guides/9_control_messages.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,24 @@ retrieved_payload = msg.payload()

msg_meta == retrieved_payload # True
```

### Conversion from `MultiMessage` to `ControlMessage`

Starting with version 24.06, the `MultiMessage` type will be deprecated, and all usage should transition to `ControlMessage`. Each `MultiMessage` functionality has a corresponding equivalent in `ControlMessage`, as illustrated below.
```python
import cudf
from morpheus.messages import MultiMessage, ControlMessage

data = cudf.DataFrame()
msg_meta = MessageMeta(data)
```

| **Functionality** | **MultiMessage** | **ControlMessage** |
| -------------------------------------------------------------- | ------------------------------------- | ------------------------------------------------------------------- |
| Initialization | `multi_msg = MultiMessage(msg_meta)` | `control_msg = ControlMessage()`<br>`control_msg.payload(msg_meta)` |
| Get columns from `cudf.DataFrame` | `multi_msg.get_meta(col_name)` | `control_msg.payload().get_data(col_name)` |
| Set columns values to `cudf.DataFrame` | `multi_msg.set_meta(col_name, value)` | `control_msg.payload().set_data(col_name, value)` |
| Get sliced `cudf.DataFrame` for given start and stop positions | `multi_msg.get_slice(start, stop)` | `control_msg.payload().get_slice(start, stop)` |
| Copy the `cudf.DataFrame` for given ranges of rows | `multi_msg.copy_ranges(ranges)` | `control_msg.payload().copy_ranges(ranges)` |

Note that the `get_slice()` and `copy_ranges()` functions in `ControlMessage` return the `MessageMeta` after slicing, whereas these functions in `MultiMessage` return a new `MultiMessage` instance.
2 changes: 1 addition & 1 deletion morpheus/messages/memory/response_memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class ResponseMemory(TensorMemory, cpp_class=_messages.ResponseMemory):
"""Output memory block holding the results of inference."""

def __new__(cls, *args, **kwargs):
morpheus_logger.deprecated_message_warning(logger, cls, TensorMemory)
morpheus_logger.deprecated_message_warning(cls, TensorMemory)
return super().__new__(cls, *args, **kwargs)

def get_output(self, name: str):
Expand Down
9 changes: 9 additions & 0 deletions morpheus/messages/message_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@
import functools
import typing

from typing_utils import issubtype

from morpheus import messages
from morpheus.config import CppConfig
from morpheus.messages import ControlMessage
from morpheus.utils import logger as morpheus_logger


class MessageImpl(abc.ABCMeta):
Expand All @@ -44,6 +49,10 @@ def __new__(cls, name, bases, namespace, /, cpp_class=None, **kwargs):
@functools.wraps(result.__new__)
def _internal_new(other_cls, *args, **kwargs):

# Instantiating MultiMessage and its subclasses from Python or C++ will generate a deprecation warning
if issubtype(other_cls, messages.MultiMessage):
morpheus_logger.deprecated_message_warning(other_cls, ControlMessage)

# If _cpp_class is set, and use_cpp is enabled, create the C++ instance
if (getattr(other_cls, "_cpp_class", None) is not None and CppConfig.get_should_use_cpp()):
return cpp_class(*args, **kwargs)
Expand Down
2 changes: 1 addition & 1 deletion morpheus/messages/multi_response_message.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ class MultiResponseProbsMessage(MultiResponseMessage, cpp_class=_messages.MultiR
required_tensors: typing.ClassVar[typing.List[str]] = ["probs"]

def __new__(cls, *args, **kwargs):
morpheus_logger.deprecated_message_warning(logger, cls, MultiResponseMessage)
morpheus_logger.deprecated_message_warning(cls, MultiResponseMessage)
return super(MultiResponseMessage, cls).__new__(cls, *args, **kwargs)

def __init__(self,
Expand Down
19 changes: 14 additions & 5 deletions morpheus/utils/logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,17 @@
import logging.handlers
import multiprocessing
import os
import re
import warnings
from enum import Enum

import appdirs
import click
import mrc
from tqdm import tqdm

import morpheus

LogLevels = Enum('LogLevels', logging._nameToLevel)


Expand Down Expand Up @@ -223,9 +227,14 @@ def deprecated_stage_warning(logger, cls, name, reason: str = None):
logger.warning(message)


def deprecated_message_warning(logger, cls, new_cls):
def deprecated_message_warning(cls, new_cls):
"""Log a warning about a deprecated message."""
logger.warning(
("The '%s' message has been deprecated and will be removed in a future version. Please use '%s' instead."),
cls.__name__,
new_cls.__name__)
match = re.match(r"(\d+\.\d+)", morpheus.__version__)
if match is None:
version = "next version"
else:
version = "version " + match.group(1)

message = (f"The '{cls.__name__}' message has been deprecated and will be removed "
f"after {version} release. Please use '{new_cls.__name__}' instead.")
warnings.warn(message, DeprecationWarning)
21 changes: 13 additions & 8 deletions tests/test_logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import logging
import multiprocessing
import os
import re
from unittest.mock import patch

import pytest
Expand Down Expand Up @@ -142,18 +143,22 @@ class DummyStage():
"This is the reason." in caplog.text


def test_deprecated_message_warning(caplog):
def test_deprecated_message_warning():

class OldMessage():
pass

class NewMessage():
pass

logger = logging.getLogger()
caplog.set_level(logging.WARNING)
deprecated_message_warning(logger, OldMessage, NewMessage)
assert len(caplog.records) == 1
assert caplog.records[0].levelname == "WARNING"
assert "The 'OldMessage' message has been deprecated and will be removed in a future version. " \
"Please use 'NewMessage' instead." in caplog.text
with pytest.warns(DeprecationWarning) as warnings:
deprecated_message_warning(OldMessage, NewMessage)

pattern_with_version = (r"The '(\w+)' message has been deprecated and will be removed "
r"after version (\d+\.\d+) release. Please use '(\w+)' instead.")

pattern_without_version = (r"The '(\w+)' message has been deprecated and will be removed "
r"after next version release. Please use '(\w+)' instead.")

assert (re.search(pattern_with_version, str(warnings[0].message)) is not None) or\
(re.search(pattern_without_version, str(warnings[0].message)))
42 changes: 42 additions & 0 deletions tests/test_multi_message.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import dataclasses
import string
import typing
from unittest.mock import patch

import cupy as cp
import numpy as np
Expand All @@ -28,6 +29,7 @@
import cudf

from _utils.dataset_manager import DatasetManager
from morpheus.messages import ControlMessage
from morpheus.messages.memory.inference_memory import InferenceMemory
from morpheus.messages.memory.response_memory import ResponseMemory
from morpheus.messages.memory.response_memory import ResponseMemoryProbs
Expand All @@ -42,6 +44,7 @@
from morpheus.messages.multi_response_message import MultiResponseMessage
from morpheus.messages.multi_response_message import MultiResponseProbsMessage
from morpheus.messages.multi_tensor_message import MultiTensorMessage
from morpheus.utils import logger as morpheus_logger


@pytest.mark.use_python
Expand Down Expand Up @@ -800,3 +803,42 @@ def test_tensor_slicing(dataset: DatasetManager):
assert double_slice.count == single_slice.count
assert cp.all(double_slice.get_tensor("probs") == single_slice.get_tensor("probs"))
dataset.assert_df_equal(double_slice.get_meta(), single_slice.get_meta())


@pytest.mark.usefixtures("use_cpp")
@pytest.mark.use_python
def test_deprecation_message(filter_probs_df: cudf.DataFrame, caplog):

meta = MessageMeta(filter_probs_df)

multi_tensor_message_tensors = {
"input_ids": cp.zeros((20, 2)),
"input_mask": cp.zeros((20, 2)),
"seq_ids": cp.expand_dims(cp.arange(0, 20, dtype=int), axis=1),
"input__0": cp.zeros((20, 2)),
"probs": cp.zeros((20, 2)),
}

def generate_deprecation_warning(deprecated_class, new_class):

# patching warning.warn here to get the warning message string from deprecated_message_warning() for asserting
with patch("warnings.warn") as mock_warning:
morpheus_logger.deprecated_message_warning(deprecated_class, new_class)
warning_msg = mock_warning.call_args.args[0]

return warning_msg

with pytest.warns(DeprecationWarning) as warnings:
MultiMessage(meta=meta)
MultiAEMessage(meta=meta, model=None)
MultiTensorMessage(meta=meta, memory=TensorMemory(count=20, tensors=multi_tensor_message_tensors))
MultiResponseMessage(meta=meta, memory=TensorMemory(count=20, tensors=multi_tensor_message_tensors))
MultiInferenceMessage(meta=meta, memory=TensorMemory(count=20, tensors=multi_tensor_message_tensors))
MultiInferenceAEMessage(meta=meta, memory=TensorMemory(count=20, tensors=multi_tensor_message_tensors))

assert str(warnings[0].message) == generate_deprecation_warning(MultiMessage, ControlMessage)
assert str(warnings[1].message) == generate_deprecation_warning(MultiAEMessage, ControlMessage)
assert str(warnings[2].message) == generate_deprecation_warning(MultiTensorMessage, ControlMessage)
assert str(warnings[3].message) == generate_deprecation_warning(MultiResponseMessage, ControlMessage)
assert str(warnings[4].message) == generate_deprecation_warning(MultiInferenceMessage, ControlMessage)
assert str(warnings[5].message) == generate_deprecation_warning(MultiInferenceAEMessage, ControlMessage)

0 comments on commit 580be43

Please sign in to comment.