diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index dfac10e129..180b0c11cc 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -18,7 +18,7 @@ services: triton: container_name: morpheus-triton runtime: nvidia - image: nvcr.io/nvidia/tritonserver:23.06-py3 + image: nvcr.io/nvidia/tritonserver:24.09-py3 command: tritonserver --model-repository=/models/triton-model-repo --exit-on-error=false ${TRITON_MODEL_ARGS} ports: - 8000:8000 diff --git a/.github/workflows/ci_pipe.yml b/.github/workflows/ci_pipe.yml index 96b2e561d7..1b6391119d 100644 --- a/.github/workflows/ci_pipe.yml +++ b/.github/workflows/ci_pipe.yml @@ -47,7 +47,7 @@ on: env: CHANGE_TARGET: "${{ github.base_ref }}" CUDA_PATH: "/usr/local/cuda/" - CUDA_VER: "12.1" + CUDA_VER: "12.5" GH_TOKEN: "${{ github.token }}" GIT_COMMIT: "${{ github.sha }}" MORPHEUS_ROOT: "${{ github.workspace }}/morpheus" diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index aea0e918ac..ba75c3090f 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -95,8 +95,8 @@ jobs: # Upload morpheus conda packages only for non PR branches. Use 'main' for main branch and 'dev' for all other branches conda_upload_label: ${{ !fromJSON(needs.prepare.outputs.is_pr) && (fromJSON(needs.prepare.outputs.is_main_branch) && 'main' || 'dev') || '' }} base_container: rapidsai/ci-conda:cuda12.5.1-ubuntu22.04-py3.10 - container: nvcr.io/ea-nvidia-morpheus/morpheus:morpheus-ci-build-241004 - test_container: nvcr.io/ea-nvidia-morpheus/morpheus:morpheus-ci-test-241004 + container: nvcr.io/ea-nvidia-morpheus/morpheus:morpheus-ci-build-241024 + test_container: nvcr.io/ea-nvidia-morpheus/morpheus:morpheus-ci-test-241024 secrets: CONDA_TOKEN: ${{ secrets.CONDA_TOKEN }} NGC_API_KEY: ${{ secrets.NGC_API_KEY }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 21ab786dbf..016ac721ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,139 @@ See the License for the specific language governing permissions and limitations under the License. --> +# Morpheus 24.10.00 (01 Nov 2024) + +## 🚨 Breaking Changes + +- Support LLM pipelines in CPU-only mode ([#1906](https://github.com/nv-morpheus/Morpheus/pull/1906)) [@dagardner-nv](https://github.com/dagardner-nv) +- Remove Starter Digital Fingerprinting (DFP) ([#1903](https://github.com/nv-morpheus/Morpheus/pull/1903)) [@efajardo-nv](https://github.com/efajardo-nv) +- Finalize removing `MultiMessage` from Morpheus ([#1886](https://github.com/nv-morpheus/Morpheus/pull/1886)) [@yczhang-nv](https://github.com/yczhang-nv) +- Add support for a CPU-only Mode ([#1851](https://github.com/nv-morpheus/Morpheus/pull/1851)) [@dagardner-nv](https://github.com/dagardner-nv) +- Removing support for `MultiMessage` from stages ([#1803](https://github.com/nv-morpheus/Morpheus/pull/1803)) [@yczhang-nv](https://github.com/yczhang-nv) + +## 🐛 Bug Fixes + +- Pin boto3 and s3fs to compatible versions to resolve access denied errors ([#2039](https://github.com/nv-morpheus/Morpheus/pull/2039)) [@dagardner-nv](https://github.com/dagardner-nv) +- Fix phishing Python API example to match CLI example ([#2037](https://github.com/nv-morpheus/Morpheus/pull/2037)) [@dagardner-nv](https://github.com/dagardner-nv) +- Model updates and cleanup following upgrade to to triton 24.09 ([#2036](https://github.com/nv-morpheus/Morpheus/pull/2036)) [@AnuradhaKaruppiah](https://github.com/AnuradhaKaruppiah) +- Copy data files needed by root_cause_analysis to examples/data ([#2032](https://github.com/nv-morpheus/Morpheus/pull/2032)) [@AnuradhaKaruppiah](https://github.com/AnuradhaKaruppiah) +- Fix for duplicate row IDs in `log_parsing` output ([#2031](https://github.com/nv-morpheus/Morpheus/pull/2031)) [@dagardner-nv](https://github.com/dagardner-nv) +- Fix `log_parsing` example pipeline null output issue ([#2024](https://github.com/nv-morpheus/Morpheus/pull/2024)) [@yczhang-nv](https://github.com/yczhang-nv) +- Fixup file paths in the modular digital fingerprinting documentation. ([#2016](https://github.com/nv-morpheus/Morpheus/pull/2016)) [@AnuradhaKaruppiah](https://github.com/AnuradhaKaruppiah) +- Fix `DeserializeStage` to ensure output messages correctly contain the correct rows for each batch ([#2015](https://github.com/nv-morpheus/Morpheus/pull/2015)) [@dagardner-nv](https://github.com/dagardner-nv) +- Fix offset calculation when taking a slice of a `SlicedMessageMeta` ([#2006](https://github.com/nv-morpheus/Morpheus/pull/2006)) [@dagardner-nv](https://github.com/dagardner-nv) +- Fix CUDF's `Column.from_column_view` by copying it and adjusting. ([#2004](https://github.com/nv-morpheus/Morpheus/pull/2004)) [@cwharris](https://github.com/cwharris) +- Fix up file paths in the DFP README ([#2003](https://github.com/nv-morpheus/Morpheus/pull/2003)) [@AnuradhaKaruppiah](https://github.com/AnuradhaKaruppiah) +- Fix AttributeError: 'int' object has no attribute 'item' ([#1995](https://github.com/nv-morpheus/Morpheus/pull/1995)) [@dagardner-nv](https://github.com/dagardner-nv) +- Fix utilities submodule commit ([#1987](https://github.com/nv-morpheus/Morpheus/pull/1987)) [@cwharris](https://github.com/cwharris) +- Update `val-run-all.sh` to run cpp pipeline only ([#1986](https://github.com/nv-morpheus/Morpheus/pull/1986)) [@yczhang-nv](https://github.com/yczhang-nv) +- Fix `onnx-to-trt` utility ([#1984](https://github.com/nv-morpheus/Morpheus/pull/1984)) [@dagardner-nv](https://github.com/dagardner-nv) +- Update Utilities submodule and fix compilation with latest build of MRC ([#1981](https://github.com/nv-morpheus/Morpheus/pull/1981)) [@mdemoret-nv](https://github.com/mdemoret-nv) +- Fix missing dependency in DFP Grafana example ([#1977](https://github.com/nv-morpheus/Morpheus/pull/1977)) [@efajardo-nv](https://github.com/efajardo-nv) +- Populate all the LFS data needed for running examples within the release container ([#1976](https://github.com/nv-morpheus/Morpheus/pull/1976)) [@AnuradhaKaruppiah](https://github.com/AnuradhaKaruppiah) +- Ensure timestamps are copied in `LLMEngineStage` ([#1975](https://github.com/nv-morpheus/Morpheus/pull/1975)) [@dagardner-nv](https://github.com/dagardner-nv) +- Install sentence-transformers via pip to avoid CPU-torch conda dependencies ([#1974](https://github.com/nv-morpheus/Morpheus/pull/1974)) [@efajardo-nv](https://github.com/efajardo-nv) +- Add `**kwargs` back to `NVFoundationLLMClient.generate_batch()` and `generate_batch_async()` ([#1967](https://github.com/nv-morpheus/Morpheus/pull/1967)) [@ashsong-nv](https://github.com/ashsong-nv) +- Benchmark updates/fixes ([#1958](https://github.com/nv-morpheus/Morpheus/pull/1958)) [@efajardo-nv](https://github.com/efajardo-nv) +- Improve test performance ([#1953](https://github.com/nv-morpheus/Morpheus/pull/1953)) [@dagardner-nv](https://github.com/dagardner-nv) +- Adopt updated utilities fix in-place Python installs ([#1952](https://github.com/nv-morpheus/Morpheus/pull/1952)) [@dagardner-nv](https://github.com/dagardner-nv) +- Update cuda version for docker containers ([#1941](https://github.com/nv-morpheus/Morpheus/pull/1941)) [@dagardner-nv](https://github.com/dagardner-nv) +- Multiple fixes related to `SharedProcessPool` & `MultiProcessingStage` ([#1940](https://github.com/nv-morpheus/Morpheus/pull/1940)) [@yczhang-nv](https://github.com/yczhang-nv) +- Fix dask error in DFP Integrated training pipeline ([#1931](https://github.com/nv-morpheus/Morpheus/pull/1931)) [@dagardner-nv](https://github.com/dagardner-nv) +- Remove `SharedProcessPool.terminate()` related tests to avoid stack traces and blocking remote-ci ([#1929](https://github.com/nv-morpheus/Morpheus/pull/1929)) [@yczhang-nv](https://github.com/yczhang-nv) +- Provide a timeout to the queue.get call in `HttpServerSourceStage` to avoid spinlocking ([#1928](https://github.com/nv-morpheus/Morpheus/pull/1928)) [@dagardner-nv](https://github.com/dagardner-nv) +- Ensure that `pytest` is able to run without optional dependencies ([#1927](https://github.com/nv-morpheus/Morpheus/pull/1927)) [@dagardner-nv](https://github.com/dagardner-nv) +- Better handle exceptions generated in the `LLMEngine` to not show the `stoul` error ([#1922](https://github.com/nv-morpheus/Morpheus/pull/1922)) [@mdemoret-nv](https://github.com/mdemoret-nv) +- Fixing the docker build when Morpheus is a submodule ([#1914](https://github.com/nv-morpheus/Morpheus/pull/1914)) [@mdemoret-nv](https://github.com/mdemoret-nv) +- Build morpheus_llm by default ([#1911](https://github.com/nv-morpheus/Morpheus/pull/1911)) [@AnuradhaKaruppiah](https://github.com/AnuradhaKaruppiah) +- Fix conda path for missing llm packages ([#1907](https://github.com/nv-morpheus/Morpheus/pull/1907)) [@dagardner-nv](https://github.com/dagardner-nv) +- Update `WriteToVectorDBStage` to re-raise errors from the underlying database ([#1905](https://github.com/nv-morpheus/Morpheus/pull/1905)) [@dagardner-nv](https://github.com/dagardner-nv) +- Avoid memory leak warnings from `pypdfium2` ([#1902](https://github.com/nv-morpheus/Morpheus/pull/1902)) [@dagardner-nv](https://github.com/dagardner-nv) +- Remove redundant copy of the `load_labels_file` method ([#1901](https://github.com/nv-morpheus/Morpheus/pull/1901)) [@dagardner-nv](https://github.com/dagardner-nv) +- Fix `Can't find 'action.yml'` CI error ([#1896](https://github.com/nv-morpheus/Morpheus/pull/1896)) [@dagardner-nv](https://github.com/dagardner-nv) +- Fix DFP integrated training Azure pipeline ([#1894](https://github.com/nv-morpheus/Morpheus/pull/1894)) [@yczhang-nv](https://github.com/yczhang-nv) +- Drop 'CI Pipeline / Check' dependency from the 'package-core' job ([#1885](https://github.com/nv-morpheus/Morpheus/pull/1885)) [@AnuradhaKaruppiah](https://github.com/AnuradhaKaruppiah) +- Python source stages now optionally receive a reference to `mrc.Subscription` ([#1881](https://github.com/nv-morpheus/Morpheus/pull/1881)) [@dagardner-nv](https://github.com/dagardner-nv) +- Fix `Unregistered type : mrc::pymrc::coro::BoostFibersMainPyAwaitable` error ([#1869](https://github.com/nv-morpheus/Morpheus/pull/1869)) [@dagardner-nv](https://github.com/dagardner-nv) +- Revert PR_1736 "Always run the PR builder step even if others are cancelled" ([#1860](https://github.com/nv-morpheus/Morpheus/pull/1860)) [@AnuradhaKaruppiah](https://github.com/AnuradhaKaruppiah) +- ensure columns are strings before concatenation ([#1857](https://github.com/nv-morpheus/Morpheus/pull/1857)) [@cwharris](https://github.com/cwharris) +- Update Kafka DL script to `2.13-3.8.0` ([#1856](https://github.com/nv-morpheus/Morpheus/pull/1856)) [@cwharris](https://github.com/cwharris) +- Update `isort` settings file path in `fix_all.sh` ([#1855](https://github.com/nv-morpheus/Morpheus/pull/1855)) [@yczhang-nv](https://github.com/yczhang-nv) +- Move isort settings into pyproject.toml ([#1854](https://github.com/nv-morpheus/Morpheus/pull/1854)) [@dagardner-nv](https://github.com/dagardner-nv) +- Update location of morpheus setup and data files in VS settings ([#1843](https://github.com/nv-morpheus/Morpheus/pull/1843)) [@AnuradhaKaruppiah](https://github.com/AnuradhaKaruppiah) +- Fix isort config marking `_utils` as known first party ([#1842](https://github.com/nv-morpheus/Morpheus/pull/1842)) [@dagardner-nv](https://github.com/dagardner-nv) +- Fix usage of the C++ impl of `write_df_to_file` ([#1840](https://github.com/nv-morpheus/Morpheus/pull/1840)) [@dagardner-nv](https://github.com/dagardner-nv) +- Fix shutdown on Ctrl+C for Python source stages ([#1839](https://github.com/nv-morpheus/Morpheus/pull/1839)) [@dagardner-nv](https://github.com/dagardner-nv) +- Improved type-hints for stage and source decorators ([#1831](https://github.com/nv-morpheus/Morpheus/pull/1831)) [@dagardner-nv](https://github.com/dagardner-nv) +- Add tests to confirm that a mis-configured MultiPortModulesStage will raise an exception rather than segfaulting ([#1829](https://github.com/nv-morpheus/Morpheus/pull/1829)) [@dagardner-nv](https://github.com/dagardner-nv) +- Ensure proper initialization of `CMAKE_INSTALL_PREFIX` if needed ([#1815](https://github.com/nv-morpheus/Morpheus/pull/1815)) [@dagardner-nv](https://github.com/dagardner-nv) +- Fix merging of CLI args and Yaml configs in `vdb_upload` example ([#1813](https://github.com/nv-morpheus/Morpheus/pull/1813)) [@dagardner-nv](https://github.com/dagardner-nv) +- Fix log parsing undefined variable and duplicate sequence id errors ([#1809](https://github.com/nv-morpheus/Morpheus/pull/1809)) [@dagardner-nv](https://github.com/dagardner-nv) +- Remove obsolete version string from compose yamls ([#1808](https://github.com/nv-morpheus/Morpheus/pull/1808)) [@dagardner-nv](https://github.com/dagardner-nv) +- Ensure the release container does not contain any unintended files ([#1807](https://github.com/nv-morpheus/Morpheus/pull/1807)) [@dagardner-nv](https://github.com/dagardner-nv) +- Update `ci/release/update-version.sh` to include missed files ([#1801](https://github.com/nv-morpheus/Morpheus/pull/1801)) [@dagardner-nv](https://github.com/dagardner-nv) + +## 📖 Documentation + +- Add known issue for dask shutdown ([#2027](https://github.com/nv-morpheus/Morpheus/pull/2027)) [@cwharris](https://github.com/cwharris) +- Set the version in the conda packages docs ([#2017](https://github.com/nv-morpheus/Morpheus/pull/2017)) [@AnuradhaKaruppiah](https://github.com/AnuradhaKaruppiah) +- Fix mis-leading deserialize stage comments ([#2009](https://github.com/nv-morpheus/Morpheus/pull/2009)) [@dagardner-nv](https://github.com/dagardner-nv) +- Update Morpheus docs to use cuda 12.5 ([#2008](https://github.com/nv-morpheus/Morpheus/pull/2008)) [@yczhang-nv](https://github.com/yczhang-nv) +- Fix minor issues with LLM example documentation ([#1992](https://github.com/nv-morpheus/Morpheus/pull/1992)) [@dagardner-nv](https://github.com/dagardner-nv) +- Incorporate review comments in the conda packages documentation ([#1982](https://github.com/nv-morpheus/Morpheus/pull/1982)) [@AnuradhaKaruppiah](https://github.com/AnuradhaKaruppiah) +- Add CPU-only documentation ([#1969](https://github.com/nv-morpheus/Morpheus/pull/1969)) [@dagardner-nv](https://github.com/dagardner-nv) +- Document each of the Conda environment files ([#1932](https://github.com/nv-morpheus/Morpheus/pull/1932)) [@dagardner-nv](https://github.com/dagardner-nv) +- Update documentation to reflect CPU-only execution mode ([#1924](https://github.com/nv-morpheus/Morpheus/pull/1924)) [@dagardner-nv](https://github.com/dagardner-nv) +- Remove `TODO` statements from documentation ([#1879](https://github.com/nv-morpheus/Morpheus/pull/1879)) [@dagardner-nv](https://github.com/dagardner-nv) +- Remove automock for merlin as we no longer have/use merlin ([#1830](https://github.com/nv-morpheus/Morpheus/pull/1830)) [@dagardner-nv](https://github.com/dagardner-nv) +- Add documentation checks to CI ([#1821](https://github.com/nv-morpheus/Morpheus/pull/1821)) [@dagardner-nv](https://github.com/dagardner-nv) +- Fix documentation links to work in both source repo and documentation builds ([#1814](https://github.com/nv-morpheus/Morpheus/pull/1814)) [@dagardner-nv](https://github.com/dagardner-nv) +- Update documentation for `vdb_upload` to use realistic source data with the `--file_source` flag ([#1800](https://github.com/nv-morpheus/Morpheus/pull/1800)) [@dagardner-nv](https://github.com/dagardner-nv) + +## 🚀 New Features + +- Install morpheus-dfp conda package in the DFP container ([#1971](https://github.com/nv-morpheus/Morpheus/pull/1971)) [@AnuradhaKaruppiah](https://github.com/AnuradhaKaruppiah) +- Morpheus docs update post compartmentalization ([#1964](https://github.com/nv-morpheus/Morpheus/pull/1964)) [@AnuradhaKaruppiah](https://github.com/AnuradhaKaruppiah) +- Adding implementation of Router Nodes ([#1963](https://github.com/nv-morpheus/Morpheus/pull/1963)) [@mdemoret-nv](https://github.com/mdemoret-nv) +- Include requirements files in the morpheus packages ([#1957](https://github.com/nv-morpheus/Morpheus/pull/1957)) [@AnuradhaKaruppiah](https://github.com/AnuradhaKaruppiah) +- Unit tests for the namespace update script ([#1954](https://github.com/nv-morpheus/Morpheus/pull/1954)) [@AnuradhaKaruppiah](https://github.com/AnuradhaKaruppiah) +- Script for updating the namespace due to compartmentalization changes ([#1946](https://github.com/nv-morpheus/Morpheus/pull/1946)) [@AnuradhaKaruppiah](https://github.com/AnuradhaKaruppiah) +- Move tests/common to tests/morpheus/common ([#1942](https://github.com/nv-morpheus/Morpheus/pull/1942)) [@AnuradhaKaruppiah](https://github.com/AnuradhaKaruppiah) +- Refactor Morpheus unit tests and plugin to the conda recipe for per-lib testing ([#1933](https://github.com/nv-morpheus/Morpheus/pull/1933)) [@AnuradhaKaruppiah](https://github.com/AnuradhaKaruppiah) +- Remove debug log in `HttpServerSourceStage` when the queue is empty ([#1921](https://github.com/nv-morpheus/Morpheus/pull/1921)) [@dagardner-nv](https://github.com/dagardner-nv) +- Refactor digital_fingerprinting stages and add morpheus-split conda recipe (core, dfp, llm) ([#1897](https://github.com/nv-morpheus/Morpheus/pull/1897)) [@AnuradhaKaruppiah](https://github.com/AnuradhaKaruppiah) +- Move vector db stages to morpheus-llm ([#1889](https://github.com/nv-morpheus/Morpheus/pull/1889)) [@AnuradhaKaruppiah](https://github.com/AnuradhaKaruppiah) +- Scripts for building and uploading the morpheus-core conda package ([#1883](https://github.com/nv-morpheus/Morpheus/pull/1883)) [@AnuradhaKaruppiah](https://github.com/AnuradhaKaruppiah) +- Implement `MultiProcessingStage` ([#1878](https://github.com/nv-morpheus/Morpheus/pull/1878)) [@yczhang-nv](https://github.com/yczhang-nv) +- Update to RAPIDS 24.10 ([#1874](https://github.com/nv-morpheus/Morpheus/pull/1874)) [@cwharris](https://github.com/cwharris) +- Add support for a CPU-only Mode ([#1851](https://github.com/nv-morpheus/Morpheus/pull/1851)) [@dagardner-nv](https://github.com/dagardner-nv) +- [morpheus-refactor] Move morpheus source to python/morpheus ([#1836](https://github.com/nv-morpheus/Morpheus/pull/1836)) [@AnuradhaKaruppiah](https://github.com/AnuradhaKaruppiah) +- Support for `ControlMessage` as an output type for `HttpServerSourceStage` and `HttpClientSourceStage` ([#1834](https://github.com/nv-morpheus/Morpheus/pull/1834)) [@dagardner-nv](https://github.com/dagardner-nv) +- Remove NVTabular ([#1825](https://github.com/nv-morpheus/Morpheus/pull/1825)) [@cwharris](https://github.com/cwharris) +- Create a Docker image for Morpheus models ([#1804](https://github.com/nv-morpheus/Morpheus/pull/1804)) [@dagardner-nv](https://github.com/dagardner-nv) +- Add unique column to output of the `log_parsing` pipeline ([#1795](https://github.com/nv-morpheus/Morpheus/pull/1795)) [@dagardner-nv](https://github.com/dagardner-nv) + +## 🛠️ Improvements + +- Update to Triton Inference Server container version 24.09 ([#2001](https://github.com/nv-morpheus/Morpheus/pull/2001)) [@dagardner-nv](https://github.com/dagardner-nv) +- Remove temporary DFP todo list ([#1998](https://github.com/nv-morpheus/Morpheus/pull/1998)) [@AnuradhaKaruppiah](https://github.com/AnuradhaKaruppiah) +- devcontainer: replace `VAULT_HOST` with `AWS_ROLE_ARN` ([#1962](https://github.com/nv-morpheus/Morpheus/pull/1962)) [@jjacobelli](https://github.com/jjacobelli) +- Reduce the number of warnings emitted ([#1947](https://github.com/nv-morpheus/Morpheus/pull/1947)) [@dagardner-nv](https://github.com/dagardner-nv) +- Set lower CPU usage for `test_shared_process_pool.py` to avoid slowing down the test ([#1935](https://github.com/nv-morpheus/Morpheus/pull/1935)) [@yczhang-nv](https://github.com/yczhang-nv) +- Remove unused pymysql dependency from DFP mlflow container ([#1930](https://github.com/nv-morpheus/Morpheus/pull/1930)) [@dagardner-nv](https://github.com/dagardner-nv) +- Support LLM pipelines in CPU-only mode ([#1906](https://github.com/nv-morpheus/Morpheus/pull/1906)) [@dagardner-nv](https://github.com/dagardner-nv) +- Remove Starter Digital Fingerprinting (DFP) ([#1903](https://github.com/nv-morpheus/Morpheus/pull/1903)) [@efajardo-nv](https://github.com/efajardo-nv) +- Finalize removing `MultiMessage` from Morpheus ([#1886](https://github.com/nv-morpheus/Morpheus/pull/1886)) [@yczhang-nv](https://github.com/yczhang-nv) +- Run pre-commit on all files, not just python ([#1880](https://github.com/nv-morpheus/Morpheus/pull/1880)) [@cwharris](https://github.com/cwharris) +- Prefer `len(os.sched_getaffinity(0))` over `os.cpu_count()` ([#1866](https://github.com/nv-morpheus/Morpheus/pull/1866)) [@cwharris](https://github.com/cwharris) +- Remove cloudtrail debug log from autoencoder source stage ([#1865](https://github.com/nv-morpheus/Morpheus/pull/1865)) [@cwharris](https://github.com/cwharris) +- Run yapf, flake8, isort as part of pre-commit ([#1859](https://github.com/nv-morpheus/Morpheus/pull/1859)) [@cwharris](https://github.com/cwharris) +- Warn when `Config`'s `pipeline_batch_size < model_max_batch_size` ([#1858](https://github.com/nv-morpheus/Morpheus/pull/1858)) [@cwharris](https://github.com/cwharris) +- Breakout morpheus_llm ([#1853](https://github.com/nv-morpheus/Morpheus/pull/1853)) [@AnuradhaKaruppiah](https://github.com/AnuradhaKaruppiah) +- Install built documentation into release container ([#1806](https://github.com/nv-morpheus/Morpheus/pull/1806)) [@dagardner-nv](https://github.com/dagardner-nv) +- Removing support for `MultiMessage` from stages ([#1803](https://github.com/nv-morpheus/Morpheus/pull/1803)) [@yczhang-nv](https://github.com/yczhang-nv) +- Batch incoming DOCA raw packet data ([#1731](https://github.com/nv-morpheus/Morpheus/pull/1731)) [@dagardner-nv](https://github.com/dagardner-nv) + # Morpheus 24.06.01 (23 Aug 2024) ## 🛠️ Improvements diff --git a/ci/iwyu/mappings.imp b/ci/iwyu/mappings.imp index a087b65fbe..80618e58f0 100644 --- a/ci/iwyu/mappings.imp +++ b/ci/iwyu/mappings.imp @@ -8,6 +8,7 @@ { "include": [ "\"mrc/protos/tensor_meta_data.pb.h\"", private, "", "public" ] }, # stdlib +{ "include": [ "", private, "", "public" ] }, { "include": [ "", private, "", "public" ] }, { "include": [ "", private, "", "public" ] }, { "include": [ "", private, "", "public" ] }, @@ -29,6 +30,8 @@ { "include": ["\"cuda_runtime_api.h\"", "private", "", "public"] }, { "include": ["", "private", "", "public"] }, { "include": ["\"driver_types.h\"", "private", "", "public"] }, +{ "include": ["\"cuda/__memory_resource/properties.h\"", "private", "", "public"] }, +{ "include": ["\"cuda/__memory_resource/resource_ref.h\"", "private", "", "public"] }, # gtest { "include": ["@", "private", "", "public"] }, @@ -47,7 +50,7 @@ { "include": [ "", private, "", "public" ] }, # pybind11 -{ "include": [ "", "private", "", "public" ] }, +{ "include": [ "@", "private", "", "public" ] }, { "include": [ "", "private", "", "public" ] }, # rxcpp diff --git a/ci/runner/Dockerfile b/ci/runner/Dockerfile index e3d7347268..fcd2ed3dc3 100644 --- a/ci/runner/Dockerfile +++ b/ci/runner/Dockerfile @@ -35,12 +35,6 @@ SHELL ["/bin/bash", "-c"] # Create conda environment COPY ./dependencies.yaml /tmp/conda/ -RUN <=1.13.*" -conda clean -aipty -EOF - # ============ build ================== FROM base as build diff --git a/ci/scripts/github/test.sh b/ci/scripts/github/test.sh index c26b70011b..cc901415e2 100755 --- a/ci/scripts/github/test.sh +++ b/ci/scripts/github/test.sh @@ -21,12 +21,7 @@ source ${WORKSPACE}/ci/scripts/github/morpheus_env.sh source ${WORKSPACE}/ci/scripts/github/cmake_all.sh /usr/bin/nvidia-smi -rapids-dependency-file-generator \ - --output conda \ - --file-key all \ - --matrix "cuda=${RAPIDS_CUDA_VERSION%.*};arch=$(arch);py=${RAPIDS_PY_VERSION}" | tee "${WORKSPACE_TMP}/env.yaml" - -update_conda_env "${WORKSPACE_TMP}/env.yaml" +update_conda_env "${WORKSPACE}/conda/environments/all_cuda-125_arch-x86_64.yaml" log_toolchain diff --git a/ci/scripts/run_ci_local.sh b/ci/scripts/run_ci_local.sh index d84c096a0c..b598d17be0 100755 --- a/ci/scripts/run_ci_local.sh +++ b/ci/scripts/run_ci_local.sh @@ -58,7 +58,7 @@ GIT_BRANCH=$(git branch --show-current) GIT_COMMIT=$(git log -n 1 --pretty=format:%H) LOCAL_CI_TMP=${LOCAL_CI_TMP:-${MORPHEUS_ROOT}/.tmp/local_ci_tmp} -CONTAINER_VER=${CONTAINER_VER:-241004} +CONTAINER_VER=${CONTAINER_VER:-241024} CUDA_VER=${CUDA_VER:-12.5} CUDA_FULL_VER=${CUDA_FULL_VER:-12.5.1} DOCKER_EXTRA_ARGS=${DOCKER_EXTRA_ARGS:-""} diff --git a/conda/environments/all_cuda-125_arch-x86_64.yaml b/conda/environments/all_cuda-125_arch-x86_64.yaml index 004e202a48..6195edb574 100644 --- a/conda/environments/all_cuda-125_arch-x86_64.yaml +++ b/conda/environments/all_cuda-125_arch-x86_64.yaml @@ -15,7 +15,7 @@ dependencies: - automake=1.16.5 - beautifulsoup4=4.12 - benchmark=1.8.3 -- boto3 +- boto3=1.35 - breathe=4.35.0 - c-ares=1.32 - ccache @@ -88,6 +88,7 @@ dependencies: - pydantic - pylibcudf=24.10 - pylint=3.0.3 +- pynvml=11.4 - pypdf=3.17.4 - pypdfium2=4.30 - pytest-asyncio @@ -104,10 +105,9 @@ dependencies: - requests - requests-cache=1.1 - requests-toolbelt=1.0 -- s3fs +- s3fs=2024.10 - scikit-build=0.17.6 - scikit-learn=1.3.2 -- sentence-transformers=2.7 - sphinx - sphinx_rtd_theme - sqlalchemy<2.0 @@ -140,5 +140,7 @@ dependencies: - nemollm==0.3.5 - pymilvus==2.3.6 - pytest-kafka==0.6.0 + - python-logging-loki + - sentence-transformers==2.7 - torch==2.4.0+cu124 name: all_cuda-125_arch-x86_64 diff --git a/conda/environments/dev_cuda-125_arch-x86_64.yaml b/conda/environments/dev_cuda-125_arch-x86_64.yaml index 40b952a1ad..0bb8977635 100644 --- a/conda/environments/dev_cuda-125_arch-x86_64.yaml +++ b/conda/environments/dev_cuda-125_arch-x86_64.yaml @@ -73,6 +73,7 @@ dependencies: - pydantic - pylibcudf=24.10 - pylint=3.0.3 +- pynvml=11.4 - pypdfium2=4.30 - pytest-asyncio - pytest-benchmark=4.0 diff --git a/conda/environments/examples_cuda-125_arch-x86_64.yaml b/conda/environments/examples_cuda-125_arch-x86_64.yaml index 7ca238c703..3844c3b7b7 100644 --- a/conda/environments/examples_cuda-125_arch-x86_64.yaml +++ b/conda/environments/examples_cuda-125_arch-x86_64.yaml @@ -13,7 +13,7 @@ dependencies: - appdirs - arxiv=1.4 - beautifulsoup4=4.12 -- boto3 +- boto3=1.35 - click>=8 - cudf=24.10 - cuml=24.10.* @@ -53,9 +53,8 @@ dependencies: - requests - requests-cache=1.1 - requests-toolbelt=1.0 -- s3fs +- s3fs=2024.10 - scikit-learn=1.3.2 -- sentence-transformers=2.7 - sqlalchemy<2.0 - tqdm=4 - transformers=4.36.2 @@ -78,5 +77,7 @@ dependencies: - milvus==2.3.5 - nemollm==0.3.5 - pymilvus==2.3.6 + - python-logging-loki + - sentence-transformers==2.7 - torch==2.4.0+cu124 name: examples_cuda-125_arch-x86_64 diff --git a/conda/environments/model-utils_cuda-125_arch-x86_64.yaml b/conda/environments/model-utils_cuda-125_arch-x86_64.yaml index 2957c36473..9c39634775 100644 --- a/conda/environments/model-utils_cuda-125_arch-x86_64.yaml +++ b/conda/environments/model-utils_cuda-125_arch-x86_64.yaml @@ -14,10 +14,13 @@ dependencies: - matplotlib - onnx - pandas +- pip - python=3.10 - scikit-learn=1.3.2 - seaborn - seqeval=1.2.2 - transformers=4.36.2 - xgboost +- pip: + - tensorrt-cu12 name: model-utils_cuda-125_arch-x86_64 diff --git a/dependencies.yaml b/dependencies.yaml index 867fe27b78..c237792d2a 100644 --- a/dependencies.yaml +++ b/dependencies.yaml @@ -198,6 +198,18 @@ files: - morpheus_core_pip - morpheus_llm_pip + # dfp example conda dependencies + dfp_example: + output: conda + matrix: + cuda: ["12.5"] + arch: [x86_64] + output: conda + conda_dir: examples/digital_fingerprinting/production/conda/environments + includes: + - example-dfp-container + - example-dfp-prod + channels: - conda-forge - huggingface @@ -314,6 +326,7 @@ dependencies: - include-what-you-use=0.20 - isort - pylint=3.0.3 + - pynvml=11.4 - vale=3.7 - vale-styles-microsoft - vale-styles-write-good @@ -429,10 +442,24 @@ dependencies: - output_types: [conda] packages: - *nodejs - - boto3 + - boto3=1.35 - kfp - papermill=2.4.0 - - s3fs + - s3fs=2024.10 + - pip + - pip: + - python-logging-loki + + # packages needed in the DFP example container + example-dfp-container: + common: + - output_types: [conda] + packages: + - morpheus-dfp=24.10 + - tini=0.19 + - pip: + - --extra-index-url https://download.pytorch.org/whl/cu124 + - torch==2.4.0+cu124 example-gnn: common: @@ -469,7 +496,6 @@ dependencies: - *pypdfium2 - *python-docx - requests-toolbelt=1.0 # Transitive dep needed by nemollm, specified here to ensure we get a compatible version - - sentence-transformers=2.7 - pip - pip: - langchain==0.1.16 @@ -477,6 +503,7 @@ dependencies: - faiss-cpu - google-search-results==2.4 - nemollm==0.3.5 + - sentence-transformers==2.7 # using pip now instead of conda to avoid install of pytorch cpu model-training-tuning: common: @@ -492,6 +519,9 @@ dependencies: - seaborn - seqeval=1.2.2 - xgboost + - pip + - pip: + - tensorrt-cu12 cve-mitigation: common: diff --git a/docker/Dockerfile b/docker/Dockerfile index fa146b16a5..5fa06726de 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -347,7 +347,7 @@ RUN --mount=type=bind,source=.,target=/opt/host_repo \ git clone file:///opt/host_repo/${MORPHEUS_ROOT_HOST} /tmp/morpheus_repo &&\ cd /tmp/morpheus_repo &&\ git lfs install &&\ - ./scripts/fetch_data.py fetch datasets examples + ./scripts/fetch_data.py fetch examples # ============ Stage: runtime ============ # Setup container for runtime environment @@ -360,7 +360,6 @@ COPY --from=git_clone "/tmp/morpheus_repo/conda/environments/*.yaml" "./conda/en COPY --from=git_clone "/tmp/morpheus_repo/docker" "./docker" COPY --from=build_docs "/workspace/${MORPHEUS_ROOT_HOST}/build/docs/html" "./docs" COPY --from=git_clone "/tmp/morpheus_repo/examples" "./examples" -COPY --from=git_clone "/tmp/morpheus_repo/models/datasets" "./models/datasets" COPY --from=git_clone "/tmp/morpheus_repo/scripts" "./scripts" COPY --from=git_clone "/tmp/morpheus_repo/*.md" "./" COPY --from=git_clone "/tmp/morpheus_repo/LICENSE" "./" diff --git a/docs/source/basics/cpu_only_mode.md b/docs/source/basics/cpu_only_mode.md new file mode 100644 index 0000000000..92c4cdeb21 --- /dev/null +++ b/docs/source/basics/cpu_only_mode.md @@ -0,0 +1,213 @@ + + +# Morpheus CPU-Only Mode +By default, Morpheus is designed to take advantage of the GPU for accelerated processing. However, there are cases where it may be necessary to run Morpheus on a system without access to a GPU. To address this need, Morpheus provides a CPU-only execution mode. Many stages within Morpheus require a GPU to run, while others can operate in both GPU and CPU execution mode. Attempting to add a GPU-only stage to a pipeline that is configured to operate in CPU-only mode will result in an error. + +## Execution Modes +By default, Morpheus will run in GPU execution mode. Users have the choice of specifying the execution mode with either the Python API or from the command line. + +### Python API +Execution modes are defined in the `morpheus.config.ExecutionMode` enumeration, which is then specified in the `execution_mode` attribute of the `morpheus.config.Config` object. The following example demonstrates how to set the execution mode of a pipeline to CPU-only: + +```python +from morpheus.config import Config +from morpheus.config import ExecutionMode +``` + +```python +config = Config() +config.execution_mode = ExecutionMode.CPU +``` + +> **Note**: The `execution_mode` and all other attributes of the `morpheus.config.Config` object must be set prior to constructing either the pipeline or any stage objects. The first time a `Config` object is used to construct a pipeline or stage, the `Config` object will freeze the configuration and prevent any further changes. + +#### Examples +The `examples/cpu_only`, `examples/developer_guide/1_simple_python_stage` and `examples/developer_guide/2_2_rabbitmq` examples demonstrate pipelines that are able to operate in both GPU and CPU execution modes. + +### Command Line +The `--use_cpu_only` flag is available as an option to the `morpheus run` sub-command. + +```bash +morpheus run --use_cpu_only pipeline-other --help +``` + +#### Example +The following is a simple command line example of a pipeline that can execute in CPU-only mode. To begin, ensure that you have fetched the examples dataset by running the following command from the root of the Morpheus repository: +```bash +./scripts/fetch_data.py fetch examples +``` + +Then, run the following command to run the pipeline: +```bash +morpheus --log_level=INFO \ + run --use_cpu_only pipeline-other \ + from-file --filename=examples/data/email_with_addresses.jsonlines \ + deserialize \ + monitor \ + serialize \ + to-file --filename=.tmp/output.jsonlines --overwrite +``` + +## Designing Stages for CPU Execution +It is up to the author of each stage to decide which execution modes are supported. Options are: CPU, GPU, or both. As mentioned previously, the default execution mode is GPU; authors of stages which require a GPU do not need to make any changes to their stage definitions. + +### DataFrames and Tensors +The selection of the execution mode implies selection of DataFrame and tensor types. In GPU mode, Morpheus will use [cuDF](https://docs.rapids.ai/api/cudf/stable/) DataFrames and tensors are represented as [CuPy](https://cupy.dev/) `ndarray` objects. In CPU mode, Morpheus will use [pandas](https://pandas.pydata.org/) DataFrames and [NumPy](https://numpy.org/) `ndarray` objects. + +|Mode|DataFrame|Tensor| +| -- | ------- | ---- | +|GPU|[cuDF](https://docs.rapids.ai/api/cudf/stable/)|[CuPy](https://cupy.dev/)| +|CPU|[pandas](https://pandas.pydata.org/)|[NumPy](https://numpy.org/)| + +### Stages Defined with `@stage` and `@source` Decorators +Both the `@stage` and `@source` decorators have an optional `execution_modes` parameter that accepts a tuple of `morpheus.config.ExecutionMode` values, which is used to specify the supported execution modes of the stage. + +#### CPU-only Source & Stage Examples +```python +import logging + +import pandas as pd + +from morpheus.config import Config +from morpheus.config import ExecutionMode +from morpheus.messages import MessageMeta +from morpheus.pipeline.linear_pipeline import LinearPipeline +from morpheus.pipeline.stage_decorator import source +from morpheus.pipeline.stage_decorator import stage +from morpheus.utils.logger import configure_logging + +logger = logging.getLogger(f"morpheus.{__name__}") + +@source(execution_modes=(ExecutionMode.CPU, )) +def simple_source(num_rows: int = 10) -> MessageMeta: + df = pd.DataFrame({"a": range(num_rows)}) + message = MessageMeta(df) + yield message + +@stage(execution_modes=(ExecutionMode.CPU, )) +def print_msg(msg: MessageMeta) -> MessageMeta: + logger.info(f"Receive a message with a DataFrame of type: {type(msg.df)}") + return msg + +def main(): + configure_logging(log_level=logging.INFO) + pipeline = LinearPipeline(config) + pipeline.set_source(simple_source(config)) + pipeline.add_stage(print_msg(config)) + pipeline.run() + +if __name__ == "__main__": + main() +``` + +#### CPU and GPU Source and Stage Examples +Supporting both CPU and GPU execution modes requires writing code that can handle both types of DataFrames and `ndarray` objects. In many cases, code designed to work with pandas will work with cuDF, and code designed to work with NumPy will work with CuPy, without requiring any changes to the code. However, in some cases, the API may differ slightly and there is a need to know the payload type. Care must be taken not to directly import `cudf` or any other package requiring a GPU when running in CPU mode on a system without a GPU. Morpheus provides some helper methods to assist with this in the {py:mod}`~morpheus.utils.type_utils` module, such as {py:func}`~morpheus.utils.type_utils.is_cudf_type`, {py:func}`~morpheus.utils.type_utils.get_df_class`, and {py:func}`~morpheus.utils.type_utils.get_array_pkg`. + +With a few simple modifications, the previous example now supports both CPU and GPU execution modes. The `get_df_class` function is used to determine the DataFrame type to use, and we added a command line flag to switch between the two execution modes. + +```python +import logging + +import click + +from morpheus.config import Config +from morpheus.config import ExecutionMode +from morpheus.messages import MessageMeta +from morpheus.pipeline.linear_pipeline import LinearPipeline +from morpheus.pipeline.stage_decorator import source +from morpheus.pipeline.stage_decorator import stage +from morpheus.utils.logger import configure_logging +from morpheus.utils.type_utils import get_df_class + +logger = logging.getLogger(f"morpheus.{__name__}") + + +@source(execution_modes=(ExecutionMode.GPU, ExecutionMode.CPU)) +def simple_source(num_rows: int = 10) -> MessageMeta: + df_class = get_df_class() # Returns either cudf.DataFrame or pandas.DataFrame + df = df_class({"a": range(num_rows)}) + message = MessageMeta(df) + yield message + + +@stage(execution_modes=(ExecutionMode.GPU, ExecutionMode.CPU)) +def print_msg(msg: MessageMeta) -> MessageMeta: + logger.info(f"Receive a message with a DataFrame of type: {type(msg.df)}") + return msg + + +@click.command() +@click.option('--use_cpu_only', + default=False, + type=bool, + is_flag=True, + help=("Whether or not to run in CPU only mode, setting this to True will disable C++ mode.")) +def main(use_cpu_only: bool): + configure_logging(log_level=logging.INFO) + + if use_cpu_only: + execution_mode = ExecutionMode.CPU + else: + execution_mode = ExecutionMode.GPU + + config = Config() + config.execution_mode = execution_mode + pipeline = LinearPipeline(config) + pipeline.set_source(simple_source(config)) + pipeline.add_stage(print_msg(config)) + pipeline.run() + + +if __name__ == "__main__": + main() +``` + +### Source and Stage Classes +Similar to the `@source` and `@stage` decorators, class-based sources and stages can also be defined to advertise which execution modes they support. The base class for all source and stage classes, `StageBase`, defines a `supported_execution_modes` method for this purpose, which can be overridden in a derived class. The method in the base class is defined as: + +```python +def supported_execution_modes(self) -> tuple[ExecutionMode]: + return (ExecutionMode.GPU, ) +``` + +Stage authors are free to inspect constructor arguments of the stage to determine which execution modes are supported. However, for many stages the supported execution modes do not change based upon the constructor arguments. In these cases the {py:class}`~morpheus.pipeline.execution_mode_mixins.GpuAndCpuMixin` and {py:class}`~morpheus.pipeline.execution_mode_mixins.CpuOnlyMixin` mixins can be used to simplify the implementation. + +Example class definition: +```python +from morpheus.cli.register_stage import register_stage +from morpheus.pipeline.execution_mode_mixins import GpuAndCpuMixin +from morpheus.pipeline.pass_thru_type_mixin import PassThruTypeMixin +from morpheus.pipeline.single_port_stage import SinglePortStage + + +@register_stage("pass-thru") +class PassThruStage(PassThruTypeMixin, GpuAndCpuMixin, SinglePortStage): + ... +``` + +#### GpuAndCpuMixin +In the previous decorators example, we discussed utilizing various helper methods available in the {py:mod}`~morpheus.utils.type_utils` module to assist in writing code that is able to operate in both CPU and GPU execution modes. To simplify this further, the `GpuAndCpuMixin` mixin adds these helper methods to the class. At the time of this writing, they are: + +- `df_type_str` - Returns either `"cudf"` or `"pandas"`. +- `get_df_pkg` - Returns either the `cudf` or `pandas` module. +- `get_df_class` - Returns either the `cudf.DataFrame` or `pandas.DataFrame` class. + +### Stages with C++ Implementations +C++ stages have the ability to interact with cuDF DataFrames via the [libcudf](https://docs.rapids.ai/api/libcudf/stable/) library; however, no such C++ library exists for pandas DataFrames. As a result, any stages which contain both a Python and a C++ implementation, the Python implementation will be used in CPU mode, and the C++ implementation will be used in GPU mode. For these stages, the Python implementation is then free to assume DataFrames are of type `pandas.DataFrame` and tensors are of type `numpy.ndarray`. + +A stage which contains only a C++ implementation will not be able to run in CPU mode. diff --git a/docs/source/conda_packages.md b/docs/source/conda_packages.md index 1ea5f65828..b5d34082f7 100644 --- a/docs/source/conda_packages.md +++ b/docs/source/conda_packages.md @@ -23,61 +23,68 @@ The Morpheus stages are the building blocks for creating pipelines. The stages a The libraries are hosted as Conda packages on the [`nvidia`](https://anaconda.org/nvidia/) channel. -The split into multiple libraries allows for a more modular approach to using the Morpheus stages. For example, if you are building an application for Digital Finger Printing, you can install just the `morpheus-dfp` library. This reduces the size of the installed package. It also limits the dependencies eliminating unnecessary version conflicts. +The split into multiple libraries allows for a more modular approach to using the Morpheus stages. For example, if you are building an application for Digital Finger Printing, you can install just the `morpheus-dfp` library. This reduces the size of the installed package. It also limits the dependencies, eliminating unnecessary version conflicts. ## Morpheus Core The `morpheus-core` library contains the core stages that are common across all use cases. The Morpheus core library is built from the source code in the `python/morpheus` directory of the Morpheus repository. The core library is installed as a dependency when you install any of the other Morpheus libraries. -To set up a Conda environment with the [`morpheus-core`](https://anaconda.org/nvidia/morpheus-core) library you can run the following commands: -### Create a Conda environment +To set up a Conda environment with the [`morpheus-core`](https://anaconda.org/nvidia/morpheus-core) library, you can run the following commands: + +### Create a Conda Environment ```bash export CONDA_ENV_NAME=morpheus conda create -n ${CONDA_ENV_NAME} python=3.10 conda activate ${CONDA_ENV_NAME} ``` -### Add Conda channels -These channel are required for installing the runtime dependencies + +### Add Conda Channels +These channels are required for installing the runtime dependencies: ```bash conda config --env --add channels conda-forge &&\ conda config --env --add channels nvidia &&\ conda config --env --add channels rapidsai &&\ conda config --env --add channels pytorch ``` -### Install the `morpheus-core` library + +### Install the `morpheus-core` Library ```bash -conda install -c nvidia morpheus-core +conda install -c nvidia morpheus-core=24.10 ``` -The `morpheus-core` Conda package installs the `morpheus` python package. It also pulls down all the necessary Conda runtime dependencies for the core stages including [`mrc`](https://anaconda.org/nvidia/mrc) and [`libmrc`](https://anaconda.org/nvidia/libmrc). -### Install additional PyPI dependencies -Some of the stages in the core library require additional dependencies that are hosted on PyPI. These dependencies are included as a requirements file in the `morpheus` python package. The requirements files can be located and installed by running the following command: +The `morpheus-core` Conda package installs the `morpheus` Python package. It also pulls down all the necessary Conda runtime dependencies for the core stages including [`mrc`](https://anaconda.org/nvidia/mrc) and [`libmrc`](https://anaconda.org/nvidia/libmrc). + +### Install Additional PyPI Dependencies +Some of the stages in the core library require additional dependencies that are hosted on PyPI. These dependencies are included as a requirements file in the `morpheus` Python package. The requirements files can be located and installed by running the following command: ```bash MORPHEUS_CORE_PKG_DIR=$(dirname $(python -c "import morpheus; print(morpheus.__file__)")) pip install -r ${MORPHEUS_CORE_PKG_DIR}/requirements_morpheus_core.txt ``` ## Morpheus DFP -Digital Finger Printing (DFP) is a technique used to identify anomalous behavior and uncover potential threats in the environment​. The `morpheus-dfp` library contains stages for DFP. It is built from the source code in the `python/morpheus_dfp` directory of the Morpheus repository. To set up a Conda environment with the [`morpheus-dfp`](https://anaconda.org/nvidia/morpheus-dfp) library you can run the following commands: -### Create a Conda environment +Digital Finger Printing (DFP) is a technique used to identify anomalous behavior and uncover potential threats in the environment​. The `morpheus-dfp` library contains stages for DFP. It is built from the source code in the `python/morpheus_dfp` directory of the Morpheus repository. To set up a Conda environment with the [`morpheus-dfp`](https://anaconda.org/nvidia/morpheus-dfp) library, you can run the following commands: + +### Create a Conda Environment ```bash export CONDA_ENV_NAME=morpheus-dfp conda create -n ${CONDA_ENV_NAME} python=3.10 conda activate ${CONDA_ENV_NAME} ``` -### Add Conda channels -These channel are required for installing the runtime dependencies + +### Add Conda Channels +These channels are required for installing the runtime dependencies: ```bash conda config --env --add channels conda-forge &&\ conda config --env --add channels nvidia &&\ conda config --env --add channels rapidsai &&\ conda config --env --add channels pytorch ``` -### Install the `morpheus-dfp` library + +### Install the `morpheus-dfp` Library ```bash -conda install -c nvidia morpheus-dfp +conda install -c nvidia morpheus-dfp=24.10 ``` -The `morpheus-dfp` Conda package installs the `morpheus_dfp` python package. It also pulls down all the necessary Conda runtime dependencies including [`morpheus-core`](https://anaconda.org/nvidia/morpheus-core). -### Install additional PyPI dependencies -Some of the DFP stages in the library require additional dependencies that are hosted on PyPI. These dependencies are included as a requirements file in the `morpheus_dfp` python package. And can be installed by running the following command: +The `morpheus-dfp` Conda package installs the `morpheus_dfp` Python package. It also pulls down all the necessary Conda runtime dependencies including [`morpheus-core`](https://anaconda.org/nvidia/morpheus-core). +### Install Additional PyPI dependencies +Some of the DFP stages in the library require additional dependencies that are hosted on PyPI. These dependencies are included as a requirements file in the `morpheus_dfp` Python package. It can be installed by running the following command: ```bash MORPHEUS_DFP_PKG_DIR=$(dirname $(python -c "import morpheus_dfp; print(morpheus_dfp.__file__)")) pip install -r ${MORPHEUS_DFP_PKG_DIR}/requirements_morpheus_dfp.txt @@ -85,28 +92,32 @@ pip install -r ${MORPHEUS_DFP_PKG_DIR}/requirements_morpheus_dfp.txt ## Morpheus LLM The `morpheus-llm` library contains stages for Large Language Models (LLM) and Vector Databases. These stages are used for setting up Retrieval Augmented Generation (RAG) pipelines. The `morpheus-llm` library is built from the source code in the `python/morpheus_llm` directory of the Morpheus repository. -To set up a Conda environment with the [`morpheus-llm`](https://anaconda.org/nvidia/morpheus-dfp) library you can run the following commands: -### Create a Conda environment +To set up a Conda environment with the [`morpheus-llm`](https://anaconda.org/nvidia/morpheus-dfp) library, you can run the following commands: + +### Create a Conda Environment ```bash export CONDA_ENV_NAME=morpheus-llm conda create -n ${CONDA_ENV_NAME} python=3.10 conda activate ${CONDA_ENV_NAME} ``` -### Add Conda channels -These channel are required for installing the runtime dependencies + +### Add Conda Channels +These channels are required for installing the runtime dependencies: ```bash conda config --env --add channels conda-forge &&\ conda config --env --add channels nvidia &&\ conda config --env --add channels rapidsai &&\ conda config --env --add channels pytorch ``` -### Install the `morpheus-llm` library + +### Install the `morpheus-llm` Library ```bash -conda install -c nvidia morpheus-llm +conda install -c nvidia morpheus-llm=24.10 ``` The `morpheus-llm` Conda package installs the `morpheus_llm` python package. It also pulls down all the necessary Conda packages including [`morpheus-core`](https://anaconda.org/nvidia/morpheus-core). -### Install additional PyPI dependencies -Some of the stages in the library require additional dependencies that are hosted on PyPI. These dependencies are included as a requirements file in the `morpheus_llm` python package. And can be installed by running the following command: + +### Install Additional PyPI Dependencies +Some of the stages in the library require additional dependencies that are hosted on PyPI. These dependencies are included as a requirements file in the `morpheus_llm` Python package. It can be installed by running the following command: ```bash MORPHEUS_LLM_PKG_DIR=$(dirname $(python -c "import morpheus_llm; print(morpheus_llm.__file__)")) pip install -r ${MORPHEUS_LLM_PKG_DIR}/requirements_morpheus_llm.txt @@ -114,12 +125,12 @@ pip install -r ${MORPHEUS_LLM_PKG_DIR}/requirements_morpheus_llm.txt ## Miscellaneous ### Morpheus Examples -The Morpheus examples are not included in the Morpheus Conda packages. To use them you need to clone the Morpheus repository and run the examples from source. For details refer to the [Morpheus Examples](./examples.md). +The Morpheus examples are not included in the Morpheus Conda packages. To use them, you need to clone the Morpheus repository and run the examples from source. For details, refer to the [Morpheus Examples](./examples.md). ### Namespace Updates -If you were using a Morpheus release prior to 24.10 you may need to update the namespace for the DFP, LLM and vector database stages. +If you were using a Morpheus release prior to 24.10 you may need to update the namespace for the DFP, LLM, and vector database stages. -A script, `scripts/morpheus_namespace_update.py`, has been provide to help with that and can be run as follows: +A script, `scripts/morpheus_namespace_update.py`, has been provided, and can be run with following commands: ```bash python scripts/morpheus_namespace_update.py --directory --dfp ``` diff --git a/docs/source/developer_guide/contributing.md b/docs/source/developer_guide/contributing.md index 1441936735..06fb81412e 100644 --- a/docs/source/developer_guide/contributing.md +++ b/docs/source/developer_guide/contributing.md @@ -184,7 +184,7 @@ When ready, commit both the changes to the `dependencies.yaml` file and the upda #### Prerequisites - Volta architecture GPU or better -- [CUDA 12.1](https://developer.nvidia.com/cuda-12-1-0-download-archive) +- [CUDA 12.5](https://developer.nvidia.com/cuda-12-5-0-download-archive) - `conda` - If `conda` is not installed, we recommend using the [MiniForge install guide](https://github.com/conda-forge/miniforge). This will install `conda` and set the channel default to use `conda-forge`. diff --git a/docs/source/developer_guide/guides/10_modular_pipeline_digital_fingerprinting.md b/docs/source/developer_guide/guides/10_modular_pipeline_digital_fingerprinting.md index 74ddb500cb..d55f94b86e 100644 --- a/docs/source/developer_guide/guides/10_modular_pipeline_digital_fingerprinting.md +++ b/docs/source/developer_guide/guides/10_modular_pipeline_digital_fingerprinting.md @@ -533,7 +533,7 @@ From the `examples/digital_fingerprinting/production` dir, run: ```bash docker compose run morpheus_pipeline bash ``` -To run the DFP pipelines with the example datasets within the container, run the following from `examples/digital_fingerprinting/production/morpheus`: +To run the DFP pipelines with the example datasets within the container, run the following from `examples/digital_fingerprinting/production/`: * Duo Training Pipeline ```bash @@ -543,7 +543,7 @@ To run the DFP pipelines with the example datasets within the container, run the --start_time "2022-08-01" \ --duration "60d" \ --train_users generic \ - --input_file "./control_messages/duo_payload_training.json" + --input_file "./morpheus/control_messages/duo_payload_training.json" ``` * Duo Inference Pipeline @@ -552,7 +552,7 @@ To run the DFP pipelines with the example datasets within the container, run the --log_level DEBUG \ --source duo \ --start_time "2022-08-30" \ - --input_file "./control_messages/duo_payload_inference.json" + --input_file "./morpheus/control_messages/duo_payload_inference.json" ``` * Duo Training + Inference Pipeline @@ -563,7 +563,7 @@ To run the DFP pipelines with the example datasets within the container, run the --start_time "2022-08-01" \ --duration "60d" \ --train_users generic \ - --input_file "./control_messages/duo_payload_load_train_inference.json" + --input_file "./morpheus/control_messages/duo_payload_load_train_inference.json" ``` * Azure Training Pipeline @@ -574,7 +574,7 @@ To run the DFP pipelines with the example datasets within the container, run the --start_time "2022-08-01" \ --duration "60d" \ --train_users generic \ - --input_file "./control_messages/azure_payload_training.json" + --input_file "./morpheus/control_messages/azure_payload_training.json" ``` * Azure Inference Pipeline @@ -583,7 +583,7 @@ To run the DFP pipelines with the example datasets within the container, run the --log_level DEBUG \ --source azure \ --start_time "2022-08-30" \ - --input_file "./control_messages/azure_payload_inference.json" + --input_file "./morpheus/control_messages/azure_payload_inference.json" ``` * Azure Training + Inference Pipeline @@ -594,7 +594,7 @@ To run the DFP pipelines with the example datasets within the container, run the --start_time "2022-08-01" \ --duration "60d" \ --train_users generic \ - --input_file "./control_messages/azure_payload_load_train_inference.json" + --input_file "./morpheus/control_messages/azure_payload_load_train_inference.json" ``` ### Output Fields diff --git a/docs/source/developer_guide/guides/2_real_world_phishing.md b/docs/source/developer_guide/guides/2_real_world_phishing.md index d6d27c9d9e..c460af3e02 100644 --- a/docs/source/developer_guide/guides/2_real_world_phishing.md +++ b/docs/source/developer_guide/guides/2_real_world_phishing.md @@ -487,7 +487,6 @@ import tempfile import click import morpheus -from morpheus.common import FilterSource from morpheus.config import Config from morpheus.config import PipelineModes from morpheus.pipeline import LinearPipeline @@ -495,7 +494,7 @@ from morpheus.stages.general.monitor_stage import MonitorStage from morpheus.stages.inference.triton_inference_stage import TritonInferenceStage from morpheus.stages.input.file_source_stage import FileSourceStage from morpheus.stages.output.write_to_file_stage import WriteToFileStage -from morpheus.stages.postprocess.filter_detections_stage import FilterDetectionsStage +from morpheus.stages.postprocess.add_scores_stage import AddScoresStage from morpheus.stages.postprocess.serialize_stage import SerializeStage from morpheus.stages.preprocess.deserialize_stage import DeserializeStage from morpheus.stages.preprocess.preprocess_nlp_stage import PreprocessNLPStage @@ -604,8 +603,8 @@ def run_pipeline(use_stage_function: bool, # Monitor the inference rate pipeline.add_stage(MonitorStage(config, description="Inference Rate", smoothing=0.001, unit="inf")) - # Filter values lower than 0.9 - pipeline.add_stage(FilterDetectionsStage(config, threshold=0.9, filter_source=FilterSource.TENSOR)) + # Add detection scores + pipeline.add_stage(AddScoresStage(config, labels=["is_phishing"])) # Write the to the output file pipeline.add_stage(SerializeStage(config)) @@ -1008,7 +1007,7 @@ def _build_single(self, builder: mrc.Builder, input_node: mrc.SegmentObject) -> Similar to our previous examples, most of the actual business logic of the stage is contained in the `on_data` method. In this case, we grab a reference to the DataFrane attached to the incoming message. We then serialize to an [`io.StringIO`](https://docs.python.org/3.10/library/io.html?highlight=stringio#io.StringIO) buffer, which is then sent to RabbitMQ. -> **Note**: This stage supports both GPU and CPU execution modes. When running in GPU mode, the payload of a `MessageMeta` object is always a [cuDF](https://docs.rapids.ai/api/cudf/stable/) [DataFrame](https://docs.rapids.ai/api/cudf/stable/user_guide/api_docs/dataframe/). When running in CPU mode, the payload is always a [pandas](https://pandas.pydata.org/) [DataFrane](https://pandas.pydata.org/docs/reference/frame.html). In many cases the two will be API compatible without requiring any changes to the code. In some cases however, the API may differ slightly and there is a need to know the payload type, care must be taken not to directly import `cudf` or any other package requiring a GPU when running in CPU mode on a system without a GPU. Morpheus provides some helper methods to assist with this in the {py:mod}`~morpheus.utils.type_utils` module, such as {py:func}`~morpheus.utils.type_utils.is_cudf_type` and {py:func}`~morpheus.utils.type_utils.get_df_pkg_from_obj`. +> **Note**: This stage supports both GPU and CPU execution modes. When running in GPU mode, the payload of a `MessageMeta` object is always a [cuDF](https://docs.rapids.ai/api/cudf/stable/) [DataFrame](https://docs.rapids.ai/api/cudf/stable/user_guide/api_docs/dataframe/). When running in CPU mode, the payload is always a [pandas](https://pandas.pydata.org/) [DataFrane](https://pandas.pydata.org/docs/reference/frame.html). In many cases the two will be API compatible without requiring any changes to the code. In some cases however, the API may differ slightly and there is a need to know the payload type, care must be taken not to directly import `cudf` or any other package requiring a GPU when running in CPU mode on a system without a GPU. Morpheus provides some helper methods to assist with this in the {py:mod}`~morpheus.utils.type_utils` module, such as {py:func}`~morpheus.utils.type_utils.is_cudf_type` and {py:func}`~morpheus.utils.type_utils.get_df_pkg_from_obj`. ```python def on_data(self, message: MessageMeta) -> MessageMeta: diff --git a/docs/source/developer_guide/guides/5_digital_fingerprinting.md b/docs/source/developer_guide/guides/5_digital_fingerprinting.md index e96f7526bc..f95471d5d0 100644 --- a/docs/source/developer_guide/guides/5_digital_fingerprinting.md +++ b/docs/source/developer_guide/guides/5_digital_fingerprinting.md @@ -141,6 +141,7 @@ The reference architecture is composed of the following services:​ From the root of the Morpheus repo, run: ```bash cd examples/digital_fingerprinting/production +export MORPHEUS_CONTAINER_VERSION="$(git describe --tags --abbrev=0)-runtime" docker compose build ``` @@ -161,7 +162,7 @@ From the Morpheus repo, run: ```bash conda env update --solver=libmamba \ -n ${CONDA_DEFAULT_ENV} \ - --file ./conda/environments/examples_cuda-121_arch-x86_64.yaml + --file ./conda/environments/examples_cuda-125_arch-x86_64.yaml python examples/digital_fingerprinting/fetch_example_data.py all ``` diff --git a/docs/source/extra_info/known_issues.md b/docs/source/extra_info/known_issues.md index b009da5ce9..d8173f1002 100644 --- a/docs/source/extra_info/known_issues.md +++ b/docs/source/extra_info/known_issues.md @@ -18,5 +18,6 @@ limitations under the License. # Known Issues - `vdb_upload` example pipeline triggers an internal error in Triton ([#1649](https://github.com/nv-morpheus/Morpheus/issues/1649)) +- `ransomware_detection` example pipeline occasionally logs a `distributed.comm.core.CommClosedError` error during shutdown ([#2026](https://github.com/nv-morpheus/Morpheus/issues/2026)). Refer to [open issues in the Morpheus project](https://github.com/nv-morpheus/Morpheus/issues) diff --git a/docs/source/getting_started.md b/docs/source/getting_started.md index 7c28068ff7..7e76b38bdc 100644 --- a/docs/source/getting_started.md +++ b/docs/source/getting_started.md @@ -29,16 +29,16 @@ More advanced users, or those who are interested in using the latest pre-release ## Requirements - Volta architecture GPU or better -- [CUDA 12.1](https://developer.nvidia.com/cuda-12-1-0-download-archive) +- [CUDA 12.5](https://developer.nvidia.com/cuda-12-5-0-download-archive) - [Docker](https://docs.docker.com/get-docker/) - [The NVIDIA Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html#installation) -- [NVIDIA Triton Inference Server](https://catalog.ngc.nvidia.com/orgs/nvidia/containers/tritonserver) `23.06` or higher +- [NVIDIA Triton Inference Server](https://catalog.ngc.nvidia.com/orgs/nvidia/containers/tritonserver) `24.09` or higher > **Note about Docker:** > -> The Morpheus documentation and examples assume that the [Manage Docker as a non-root user](https://docs.docker.com/engine/install/linux-postinstall/#manage-docker-as-a-non-root-user) post install step has been performed allowing Docker commands to be executed by a non-root user. This is not strictly necessary so long as the current user has `sudo` privileges to execute Docker commands. +> The Morpheus documentation and examples assume that the [Manage Docker as a non-root user](https://docs.docker.com/engine/install/linux-postinstall/#manage-docker-as-a-non-root-user) post-installation step has been performed, allowing Docker commands to be executed by a non-root user. This is not strictly necessary as long as the current user has `sudo` privileges to execute Docker commands. -## Using pre-built Docker containers +## Using Pre-Built Docker Containers ### Pull the Morpheus Image 1. Go to [https://catalog.ngc.nvidia.com/orgs/nvidia/teams/morpheus/containers/morpheus/tags](https://catalog.ngc.nvidia.com/orgs/nvidia/teams/morpheus/containers/morpheus/tags) 1. Choose a version @@ -46,7 +46,7 @@ More advanced users, or those who are interested in using the latest pre-release ```bash docker pull nvcr.io/nvidia/morpheus/morpheus:25.02-runtime ``` -1. Optional, many of the examples require NVIDIA Triton Inference Server to be running with the included models. To download the Morpheus Triton Server Models container (ensure that the version number matches that of the Morpheus container you downloaded in the previous step): +1. Optional: Many of the examples require NVIDIA Triton Inference Server to be running with the included models. To download the Morpheus Triton Server Models container, ensure that the version number matches that of the Morpheus container you downloaded in the previous step, then run: ```bash docker pull nvcr.io/nvidia/morpheus/morpheus-tritonserver-models:25.02 ``` @@ -67,10 +67,10 @@ docker run --rm -ti --runtime=nvidia --gpus=all --net=host -v /var/run/docker.so Note about some of the flags above: | Flag | Description | | ---- | ----------- | -| `--runtime=nvidia` | Choose the NVIDIA docker runtime, this enables access to the GPU inside the container. This flag isn't needed if the `nvidia` runtime is already set as the default runtime for Docker. | +| `--runtime=nvidia` | Choose the NVIDIA docker runtime. This enables access to the GPU inside the container. This flag isn't needed if the `nvidia` runtime is already set as the default runtime for Docker. | | `--gpus=all` | Specify which GPUs the container has access to. Alternately, a specific GPU could be chosen with `--gpus=` | -| `--net=host` | Most of the Morpheus pipelines utilize [NVIDIA Triton Inference Server](https://catalog.ngc.nvidia.com/orgs/nvidia/containers/tritonserver), which will be running in another container. For simplicity we will give the container access to the host system's network, production deployments may opt for an explicit network configuration. | -| `-v /var/run/docker.sock:/var/run/docker.sock` | Enables access to the Docker socket file from within the running container, this allows launching other Docker containers from within the Morpheus container. This flag is required for launching Triton with access to the included Morpheus models, users with their own models can omit this. | +| `--net=host` | Most of the Morpheus pipelines utilize [NVIDIA Triton Inference Server](https://catalog.ngc.nvidia.com/orgs/nvidia/containers/tritonserver), which will be running in another container. For simplicity, we will give the container access to the host system's network; production deployments may opt for an explicit network configuration. | +| `-v /var/run/docker.sock:/var/run/docker.sock` | Enables access to the Docker socket file from within the running container. This allows launching other Docker containers from within the Morpheus container. This flag is required for launching Triton with access to the included Morpheus models. Users with their own models can omit this. | Once launched, users wishing to launch Triton using the included Morpheus models will need to install the Docker tools in the Morpheus container by running: ```bash @@ -81,7 +81,7 @@ Skip ahead to the [Acquiring the Morpheus Models Container](#acquiring-the-morph ## Using Morpheus Conda Packages The Morpheus stages are available as libraries that are hosted on the [`nvidia`](https://anaconda.org/nvidia) Conda channel. The Morpheus Conda packages are: -[`morpheus-core`](https://anaconda.org/nvidia/morpheus-core), [`morpheus-dfp`](https://anaconda.org/nvidia/morpheus-dfp) and [`morpheus-llm`](https://anaconda.org/nvidia/morpheus-llm) +[`morpheus-core`](https://anaconda.org/nvidia/morpheus-core), [`morpheus-dfp`](https://anaconda.org/nvidia/morpheus-dfp) and [`morpheus-llm`](https://anaconda.org/nvidia/morpheus-llm). For details on these libraries and how to use them, refer to the [Morpheus Conda Packages](./conda_packages.md) guide. @@ -139,7 +139,7 @@ To assist in building the Morpheus container, several scripts have been provided ./docker/build_container_release.sh ``` -By default this will create an image named `nvcr.io/nvidia/morpheus/morpheus:${MORPHEUS_VERSION}-runtime` where `$MORPHEUS_VERSION` is replaced by the output of `git describe --tags --abbrev=0`. You can specify a different Docker image name and tag by passing the script the `DOCKER_IMAGE_NAME`, and `DOCKER_IMAGE_TAG` environment variables respectively. +By default, this will create an image named `nvcr.io/nvidia/morpheus/morpheus:${MORPHEUS_VERSION}-runtime` where `$MORPHEUS_VERSION` is replaced by the output of `git describe --tags --abbrev=0`. You can specify a different Docker image name and tag by passing the script the `DOCKER_IMAGE_NAME`, and `DOCKER_IMAGE_TAG` environment variables, respectively. To run the built "release" container, use the following: @@ -155,19 +155,19 @@ DOCKER_IMAGE_TAG="v25.02.00-runtime" ./docker/run_container_release.sh ## Acquiring the Morpheus Models Container -Many of the validation tests and example workflows require a Triton server to function. For simplicity Morpheus provides a pre-built models container which contains both Triton and the Morpheus models. Users using a release version of Morpheus can download the corresponding Triton models container from NGC with the following command: +Many of the validation tests and example workflows require a Triton server to function. For simplicity, Morpheus provides a pre-built models container, which contains both the Triton and Morpheus models. Users implementing a release version of Morpheus can download the corresponding Triton models container from NGC with the following command: ```bash docker pull nvcr.io/nvidia/morpheus/morpheus-tritonserver-models:25.02 ``` -Users working with an unreleased development version of Morpheus can build the Triton models container from the Morpheus repository. To build the Triton models container, from the root of the Morpheus repository run the following command: +Users working with an unreleased development version of Morpheus can build the Triton models container from the Morpheus repository. To build the Triton models container, run the following command from the root of the Morpheus repository: ```bash models/docker/build_container.sh ``` ## Launching Triton Server -In a new terminal use the following command to launch a Docker container for Triton loading all of the included pre-trained models: +In a new terminal, use the following command to launch a Docker container for Triton loading all of the included pre-trained models: ```bash docker run --rm -ti --gpus=all -p8000:8000 -p8001:8001 -p8002:8002 \ nvcr.io/nvidia/morpheus/morpheus-tritonserver-models:25.02 \ @@ -178,9 +178,9 @@ docker run --rm -ti --gpus=all -p8000:8000 -p8001:8001 -p8002:8002 \ --disable-auto-complete-config ``` -This will launch Triton using the default network ports (8000 for HTTP, 8001 for GRPC, and 8002 for metrics), loading all of the examples models in the Morpheus repo. +This will launch Triton using the default network ports (8000 for HTTP, 8001 for GRPC, and 8002 for metrics), loading all of the example models in the Morpheus repo. -Note: The above command is useful for testing out Morpheus, however it does load several models into GPU memory, which at time of writing consumes roughly 2GB of GPU memory. Production users should consider only loading the specific models they plan on using with the `--model-control-mode=explicit` and `--load-model` flags. For example to launch Triton only loading the `abp-nvsmi-xgb` model: +Note: The above command is useful for testing out Morpheus, however it does load several models into GPU memory, which at the time of this writing consumes roughly 2GB of GPU memory. Production users should consider only loading the specific models they plan on using with the `--model-control-mode=explicit` and `--load-model` flags. For example, to launch Triton only loading the `abp-nvsmi-xgb` model: ```bash docker run --rm -ti --gpus=all -p8000:8000 -p8001:8001 -p8002:8002 \ nvcr.io/nvidia/morpheus/morpheus-tritonserver-models:25.02 \ @@ -193,11 +193,11 @@ docker run --rm -ti --gpus=all -p8000:8000 -p8001:8001 -p8002:8002 \ --load-model abp-nvsmi-xgb ``` -Alternately, for users who have checked out the Morpheus git repository, launching the Triton server container directly mounting the models from the repository is an option. This approach is most useful for users training their own models. From the root of the Morpheus repo, use the following command to launch a Docker container for Triton loading all of the included pre-trained models: +Alternately, for users who have checked out the Morpheus git repository, launching the Triton server container directly, mounting the models from the repository is an option. This approach is most useful for users training their own models. From the root of the Morpheus repo, use the following command to launch a Docker container for Triton loading all of the included pre-trained models: ```bash docker run --rm -ti --gpus=all -p8000:8000 -p8001:8001 -p8002:8002 \ -v $PWD/models:/models \ - nvcr.io/nvidia/tritonserver:23.06-py3 \ + nvcr.io/nvidia/tritonserver:24.09-py3 \ tritonserver --model-repository=/models/triton-model-repo \ --exit-on-error=false \ --log-info=true \ @@ -215,7 +215,7 @@ For full example pipelines using both the Python API and command line interface, ### Morpheus Python Interface -The Morpheus Python interface allows users to configure their pipelines using a Python script file. This is ideal for users who are working in a Jupyter Notebook, and users who need complex initialization logic. Documentation on using both the Morpheus Python & C++ APIs can be found in the [Morpheus Developer Guide](./developer_guide/guides.md). +The Morpheus Python interface allows users to configure their pipelines using a Python script file. This is ideal for users who are working in a Jupyter Notebook, and users who need complex initialization logic. Documentation on using both the Morpheus Python and C++ APIs can be found in the [Morpheus Developer Guide](./developer_guide/guides.md). ### Morpheus Command Line Interface (CLI) @@ -243,7 +243,7 @@ Commands: tools Run a utility tool ``` -Each command in the CLI has its own help information. Use `morpheus [command] [...sub-command] --help` to get instructions for each command and sub command. For example: +Each command in the CLI has its own help information. Use `morpheus [command] [...sub-command] --help` to get instructions for each command and sub-command. For example: ```bash $ morpheus run pipeline-nlp inf-triton --help @@ -295,7 +295,7 @@ Added stage: . Accepted input types: (,) ``` -This indicates that the `to-file` stage cannot accept the input type of `morpheus.messages.ControlMessage`. This is because the `to-file` stage has no idea how to write that class to a file; it only knows how to write messages of type `morpheus.messages.message_meta.MessageMeta`. To ensure you have a valid pipeline, examine at the `Accepted input types: (,)` portion of the error message. This indicates you need a stage that converts from the output type of the `deserialize` stage, `morpheus.messages.ControlMessage`, to `morpheus.messages.message_meta.MessageMeta`, which is exactly what the `serialize` stage does. +This indicates that the `to-file` stage cannot accept the input type of `morpheus.messages.ControlMessage`. This is because the `to-file` stage does not know how to write that class to a file; it only knows how to write messages of type `morpheus.messages.message_meta.MessageMeta`. To ensure you have a valid pipeline, examine the `Accepted input types: (,)` portion of the error message. This indicates you need a stage that converts from the output type of the `deserialize` stage, `morpheus.messages.ControlMessage`, to `morpheus.messages.message_meta.MessageMeta`, which is exactly what the `serialize` stage does. #### Pipeline Stages @@ -375,7 +375,37 @@ Commands: trigger Buffer data until the previous stage has completed. validate Validate pipeline output for testing. ``` -Note: The available commands for different types of pipelines are not the same. This means that the same stage, when used in different pipelines, may have different options. Check the CLI help for the most up-to-date information during development. + +And for the AE pipeline: + +``` +$ morpheus run pipeline-ae --help +Usage: morpheus run pipeline-ae [OPTIONS] COMMAND1 [ARGS]... [COMMAND2 [ARGS]...]... + + + +Commands: + add-class Add detected classifications to each message. + add-scores Add probability scores to each message. + buffer (Deprecated) Buffer results. + delay (Deprecated) Delay results for a certain duration. + filter Filter message by a classification threshold. + from-azure Source stage is used to load Azure Active Directory messages. + from-cloudtrail Load messages from a CloudTrail directory. + from-duo Source stage is used to load Duo Authentication messages. + inf-pytorch Perform inference with PyTorch. + inf-triton Perform inference with Triton Inference Server. + monitor Display throughput numbers at a specific point in the pipeline. + preprocess Prepare Autoencoder input DataFrames for inference. + serialize Includes & excludes columns from messages. + timeseries Perform time series anomaly detection and add prediction. + to-file Write all messages to a file. + to-kafka Write all messages to a Kafka cluster. + train-ae Train an Autoencoder model on incoming data. + trigger Buffer data until the previous stage has completed. + validate Validate pipeline output for testing. +``` +> **Note**: The available commands for different types of pipelines are not the same. This means that the same stage may have different options when used in different pipelines. Check the CLI help for the most up-to-date information during development. ## Next Steps * [Morpheus Examples](./examples.md) - Example pipelines using both the Python API and command line interface diff --git a/docs/source/index.rst b/docs/source/index.rst index dce4a88bfd..5feb7d5f11 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -57,6 +57,7 @@ Using Morpheus * :doc:`Morpheus Conda Packages `- Using Morpheus Libraries via the pre-built Conda Packages * :doc:`basics/overview` - Brief overview of the command line interface * :doc:`basics/building_a_pipeline` - Introduction to building a pipeline using the command line interface + * :doc:`basics/cpu_only_mode` - Running Morpheus and designing stages for CPU-only execution mode * :doc:`Morpheus Examples ` - Example pipelines using both the Python API and command line interface * :doc:`Pretrained Models ` - Pretrained models with corresponding training, validation scripts, and datasets * :doc:`Developer Guides ` - Covers extending Morpheus with custom stages @@ -80,6 +81,7 @@ Deploying Morpheus conda_packages basics/overview basics/building_a_pipeline + basics/cpu_only_mode models_and_datasets examples/index developer_guide/guides/index diff --git a/examples/abp_nvsmi_detection/README.md b/examples/abp_nvsmi_detection/README.md index 67b81385da..244d729420 100644 --- a/examples/abp_nvsmi_detection/README.md +++ b/examples/abp_nvsmi_detection/README.md @@ -63,7 +63,7 @@ This example can be easily applied to datasets generated from your own NVIDIA GP pyNVML is not installed by default, use the following command to install it: ```bash -conda env update --solver=libmamba -n morpheus --file conda/environments/examples_cuda-121_arch-x86_64.yaml +conda env update --solver=libmamba -n morpheus --file conda/environments/examples_cuda-125_arch-x86_64.yaml ``` Run the following to start generating your dataset: @@ -127,7 +127,7 @@ morpheus --log_level=DEBUG \ pipeline-fil --columns_file=data/columns_fil.txt \ `# 1st Stage: Read from file` \ from-file --filename=examples/data/nvsmi.jsonlines \ - `# 2nd Stage: Deserialize from JSON strings to objects` \ + `# 2nd Stage: Deserialize batch DataFrame into ControlMessages` \ deserialize \ `# 3rd Stage: Preprocessing converts the input data into BERT tokens` \ preprocess \ diff --git a/examples/data/log-parsing-config-20220418.json b/examples/data/log-parsing-config-20220418.json new file mode 100644 index 0000000000..e0f49634ab --- /dev/null +++ b/examples/data/log-parsing-config-20220418.json @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:08362b4047519a1fc812a2ac4290e3d8c87273a1672e51367233ea8e9d069e0c +size 1984 diff --git a/examples/data/log-parsing-validation-data-input.csv b/examples/data/log-parsing-validation-data-input.csv new file mode 100644 index 0000000000..8fe44a8994 --- /dev/null +++ b/examples/data/log-parsing-validation-data-input.csv @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:782ac462c0d32f2f68428db825fabb8e4bbab20cb441d163c54d81539ece46c6 +size 1037235 diff --git a/examples/data/root-cause-validation-data-input.jsonlines b/examples/data/root-cause-validation-data-input.jsonlines new file mode 100644 index 0000000000..ec3967164a --- /dev/null +++ b/examples/data/root-cause-validation-data-input.jsonlines @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:20eb2598500427491f957306ac75c8b9ed4a172b3b39ffa2723a9b9e0bdec0b6 +size 46416 diff --git a/examples/developer_guide/2_1_real_world_phishing/run.py b/examples/developer_guide/2_1_real_world_phishing/run.py index 32e53042f7..64ae7d77dc 100755 --- a/examples/developer_guide/2_1_real_world_phishing/run.py +++ b/examples/developer_guide/2_1_real_world_phishing/run.py @@ -24,7 +24,6 @@ from recipient_features_stage_deco import recipient_features_stage import morpheus -from morpheus.common import FilterSource from morpheus.config import Config from morpheus.config import PipelineModes from morpheus.pipeline import LinearPipeline @@ -32,7 +31,7 @@ from morpheus.stages.inference.triton_inference_stage import TritonInferenceStage from morpheus.stages.input.file_source_stage import FileSourceStage from morpheus.stages.output.write_to_file_stage import WriteToFileStage -from morpheus.stages.postprocess.filter_detections_stage import FilterDetectionsStage +from morpheus.stages.postprocess.add_scores_stage import AddScoresStage from morpheus.stages.postprocess.serialize_stage import SerializeStage from morpheus.stages.preprocess.deserialize_stage import DeserializeStage from morpheus.stages.preprocess.preprocess_nlp_stage import PreprocessNLPStage @@ -139,8 +138,8 @@ def run_pipeline(use_stage_function: bool, # Monitor the inference rate pipeline.add_stage(MonitorStage(config, description="Inference Rate", smoothing=0.001, unit="inf")) - # Filter values lower than 0.9 - pipeline.add_stage(FilterDetectionsStage(config, threshold=0.9, filter_source=FilterSource.TENSOR)) + # Add detection scores + pipeline.add_stage(AddScoresStage(config, labels=["is_phishing"])) # Write the to the output file pipeline.add_stage(SerializeStage(config)) diff --git a/examples/developer_guide/3_simple_cpp_stage/src/simple_cpp_stage/_lib/pass_thru.hpp b/examples/developer_guide/3_simple_cpp_stage/src/simple_cpp_stage/_lib/pass_thru.hpp index 9aec2e30fc..66b76ccebb 100644 --- a/examples/developer_guide/3_simple_cpp_stage/src/simple_cpp_stage/_lib/pass_thru.hpp +++ b/examples/developer_guide/3_simple_cpp_stage/src/simple_cpp_stage/_lib/pass_thru.hpp @@ -17,11 +17,11 @@ #pragma once -#include // for exporting symbols +#include // for exporting symbols #include // for ControlMessage -#include // for Segment Builder -#include // for Segment Object -#include // for PythonNode +#include // for Segment Builder +#include // for Segment Object +#include // for PythonNode #include #include diff --git a/examples/digital_fingerprinting/production/Dockerfile b/examples/digital_fingerprinting/production/Dockerfile index 102158fd26..2c919d68b4 100644 --- a/examples/digital_fingerprinting/production/Dockerfile +++ b/examples/digital_fingerprinting/production/Dockerfile @@ -13,22 +13,27 @@ # See the License for the specific language governing permissions and # limitations under the License. -ARG MORPHEUS_CONTAINER=nvcr.io/nvidia/morpheus/morpheus -ARG MORPHEUS_CONTAINER_VERSION=v25.02.00-runtime +ARG BASE_IMG=nvcr.io/nvidia/cuda +ARG BASE_IMG_TAG=12.5.1-base-ubuntu22.04 -FROM ${MORPHEUS_CONTAINER}:${MORPHEUS_CONTAINER_VERSION} as base +FROM ${BASE_IMG}:${BASE_IMG_TAG} as base -# # Fix the entrypoint to work with different WORKDIR -ENTRYPOINT [ "/opt/conda/bin/tini", "--", "/workspace/docker/entrypoint.sh" ] +# Install necessary dependencies using apt-get +RUN apt-get update && apt-get install -y \ + git \ + git-lfs \ + wget \ + && apt-get clean -SHELL ["/bin/bash", "-c"] +# Install miniconda +RUN wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O /tmp/miniconda.sh \ + && bash /tmp/miniconda.sh -b -p /opt/conda \ + && rm /tmp/miniconda.sh -# Set the workdir to the DFP base folder -WORKDIR /workspace/examples/digital_fingerprinting/ +# Add conda to the PATH +ENV PATH=/opt/conda/bin:$PATH -# Install DFP dependencies -RUN source activate morpheus \ - && /opt/conda/bin/conda env update --solver=libmamba -n morpheus --file /workspace/conda/environments/examples_cuda-125_arch-x86_64.yaml +SHELL ["/bin/bash", "-c"] # Set the tracking URI for mlflow ENV MLFLOW_TRACKING_URI="http://mlflow:5000" @@ -36,11 +41,18 @@ ENV MLFLOW_TRACKING_URI="http://mlflow:5000" # This will get used by pipelines for the --s3_cache option # ENV DFP_S3_CACHE="/work/examples/dfp_workflow/morpheus/.s3_cache" +# Set the workdir to be the production folder +WORKDIR /workspace/examples/digital_fingerprinting/production + # If any changes have been made from the base image, recopy the sources -COPY . /workspace/examples/digital_fingerprinting/ +COPY . /workspace/examples/digital_fingerprinting/production/ -# Set the workdir to be the morpheus folder -WORKDIR /workspace/examples/digital_fingerprinting/production/morpheus +# Create a conda env with morpheus-dfp and any additional dependencies needed to run the examples +RUN conda env create --solver=libmamba -y --name morpheus-dfp --file ./conda/environments/dfp_example_cuda-125_arch-x86_64.yaml + +ENTRYPOINT [ "/opt/conda/envs/morpheus-dfp/bin/tini", "--", "/workspace/examples/digital_fingerprinting/production/docker/entrypoint.sh" ] + +SHELL ["/bin/bash", "-c"] # ===== Setup for running unattended ===== FROM base as runtime @@ -52,7 +64,7 @@ CMD ["./launch.sh"] FROM base as jupyter # Install the jupyter specific requirements -RUN source activate morpheus &&\ +RUN source activate morpheus-dfp &&\ /opt/conda/bin/conda install --solver=libmamba -y -c conda-forge \ ipywidgets \ jupyter_contrib_nbextensions \ diff --git a/examples/digital_fingerprinting/production/README.md b/examples/digital_fingerprinting/production/README.md index 69668db317..dd1be3bca4 100644 --- a/examples/digital_fingerprinting/production/README.md +++ b/examples/digital_fingerprinting/production/README.md @@ -27,17 +27,10 @@ Key Features: * Can be deployed to Kubernetes using provided Helm charts * Uses many customized stages to maximize performance. -## Build the Morpheus container -This is necessary to get the latest changes needed for DFP. From the root of the Morpheus repo: -```bash -./docker/build_container_release.sh -``` - ## Building and Running via `docker compose` ### Build ```bash cd examples/digital_fingerprinting/production -export MORPHEUS_CONTAINER_VERSION="$(git describe --tags --abbrev=0)-runtime" docker compose build ``` @@ -138,23 +131,23 @@ python $DFP_HOME/fetch_example_data.py all Run Duo Training Pipeline: ```bash -python dfp_duo_pipeline.py --train_users generic --start_time "2022-08-01" --input_file="../../../data/dfp/duo-training-data/*.json" +python dfp_duo_pipeline.py --train_users generic --start_time "2022-08-01" --input_file="../../data/dfp/duo-training-data/*.json" ``` Run Duo Inference Pipeline: ```bash -python dfp_duo_pipeline.py --train_users none --start_time "2022-08-30" --input_file="../../../data/dfp/duo-inference-data/*.json" +python dfp_duo_pipeline.py --train_users none --start_time "2022-08-30" --input_file="../../data/dfp/duo-inference-data/*.json" ``` Run Azure Training Pipeline: ```bash -python dfp_azure_pipeline.py --train_users generic --start_time "2022-08-01" --input_file="../../../data/dfp/azure-training-data/AZUREAD_2022*.json" +python dfp_azure_pipeline.py --train_users generic --start_time "2022-08-01" --input_file="../../data/dfp/azure-training-data/AZUREAD_2022*.json" ``` Run Azure Inference Pipeline: ```bash -python dfp_azure_pipeline.py --train_users none --start_time "2022-08-30" --input_file="../../../data/dfp/azure-inference-data/*.json" +python dfp_azure_pipeline.py --train_users none --start_time "2022-08-30" --input_file="../../data/dfp/azure-inference-data/*.json" ``` ##### Module-based DFP pipelines diff --git a/examples/digital_fingerprinting/production/conda/environments/dfp_example_cuda-125_arch-x86_64.yaml b/examples/digital_fingerprinting/production/conda/environments/dfp_example_cuda-125_arch-x86_64.yaml new file mode 100644 index 0000000000..929029a0ca --- /dev/null +++ b/examples/digital_fingerprinting/production/conda/environments/dfp_example_cuda-125_arch-x86_64.yaml @@ -0,0 +1,24 @@ +# This file is generated by `rapids-dependency-file-generator`. +# To make changes, edit ../../../../../dependencies.yaml and run `rapids-dependency-file-generator`. +channels: +- conda-forge +- huggingface +- rapidsai +- rapidsai-nightly +- nvidia +- nvidia/label/dev +- pytorch +dependencies: +- boto3=1.35 +- kfp +- morpheus-dfp=24.10 +- nodejs=18.* +- papermill=2.4.0 +- pip +- s3fs=2024.10 +- tini=0.19 +- pip: + - --extra-index-url https://download.pytorch.org/whl/cu124 + - python-logging-loki + - torch==2.4.0+cu124 +name: dfp_example_cuda-125_arch-x86_64 diff --git a/examples/digital_fingerprinting/production/docker-compose.yml b/examples/digital_fingerprinting/production/docker-compose.yml index 19cf16511b..99281063c8 100644 --- a/examples/digital_fingerprinting/production/docker-compose.yml +++ b/examples/digital_fingerprinting/production/docker-compose.yml @@ -72,15 +72,12 @@ services: context: ./ dockerfile: ./Dockerfile target: jupyter - args: - - MORPHEUS_CONTAINER=${MORPHEUS_CONTAINER:-nvcr.io/nvidia/morpheus/morpheus} - - MORPHEUS_CONTAINER_VERSION=${MORPHEUS_CONTAINER_VERSION:-v25.02.00-runtime} deploy: resources: reservations: devices: - - driver: nvidia - capabilities: [gpu] + - driver: nvidia + capabilities: [ gpu ] image: dfp_morpheus_jupyter container_name: jupyter ports: @@ -104,17 +101,14 @@ services: context: ./ dockerfile: ./Dockerfile target: runtime - args: - - MORPHEUS_CONTAINER=${MORPHEUS_CONTAINER:-nvcr.io/nvidia/morpheus/morpheus} - - MORPHEUS_CONTAINER_VERSION=${MORPHEUS_CONTAINER_VERSION:-v25.02.00-runtime} image: dfp_morpheus container_name: morpheus_pipeline deploy: resources: reservations: devices: - - driver: nvidia - capabilities: [gpu] + - driver: nvidia + capabilities: [ gpu ] networks: - frontend - backend @@ -123,7 +117,7 @@ services: TERM: "${TERM:-}" DFP_CACHE_DIR: "/workspace/.cache/dfp" DFP_TRACKING_URI: "http://mlflow:5000" - command: ./launch.sh --train_users=generic --duration=1d --start_time "2022-08-01" --input_file="../../../data/dfp/duo-training-data/*.json" --log_level INFO + command: ./launch.sh --train_users=generic --duration=1d --start_time "2022-08-01" --input_file="../../data/dfp/duo-training-data/*.json" --log_level INFO volumes: - ../../..:/workspace depends_on: diff --git a/examples/digital_fingerprinting/production/conda_env.yml b/examples/digital_fingerprinting/production/docker/entrypoint.sh old mode 100644 new mode 100755 similarity index 57% rename from examples/digital_fingerprinting/production/conda_env.yml rename to examples/digital_fingerprinting/production/docker/entrypoint.sh index 0eb8d3b2db..cdf5622194 --- a/examples/digital_fingerprinting/production/conda_env.yml +++ b/examples/digital_fingerprinting/production/docker/entrypoint.sh @@ -1,37 +1,27 @@ +#!/bin/bash --login # SPDX-FileCopyrightText: Copyright (c) 2021-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 -# +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -name: morpheus -channels: - - rapidsai - - nvidia - - nvidia/label/dev # For pre-releases of MRC. Should still default to full releases if available - - conda-forge -dependencies: - ####### Morpheus Dependencies (keep sorted!) ####### - - boto3 - - dask - - dill - - distributed - - kfp - - librdkafka - - mlflow>=2.10.0,<3 - - nodejs=18.* - - papermill - - s3fs>=2023.6 - ##### Pip Dependencies (keep sorted!) ####### - - pip: - - python-logging-loki +# Activate the `morpheus` conda environment. +. /opt/conda/etc/profile.d/conda.sh +conda activate morpheus-dfp + +# Source "source" file if it exists +SRC_FILE="/opt/docker/bin/entrypoint_source" +[ -f "${SRC_FILE}" ] && source "${SRC_FILE}" + +# Run whatever the user wants. +exec "$@" diff --git a/examples/digital_fingerprinting/production/morpheus/launch.sh b/examples/digital_fingerprinting/production/launch.sh similarity index 100% rename from examples/digital_fingerprinting/production/morpheus/launch.sh rename to examples/digital_fingerprinting/production/launch.sh diff --git a/examples/digital_fingerprinting/production/morpheus/benchmarks/README.md b/examples/digital_fingerprinting/production/morpheus/benchmarks/README.md index f27e29cdd4..5868e90497 100644 --- a/examples/digital_fingerprinting/production/morpheus/benchmarks/README.md +++ b/examples/digital_fingerprinting/production/morpheus/benchmarks/README.md @@ -91,13 +91,14 @@ When using the MRC SegmentModule in a pipeline, it will also require a module co To ensure the [file_to_df_loader.py](../../../../../morpheus/loaders/file_to_df_loader.py) utilizes the same type of downloading mechanism, set `MORPHEUS_FILE_DOWNLOAD_TYPE` environment variable with any one of given choices (`dask`, `dask thread`, `single thread`). -``` +```bash export MORPHEUS_FILE_DOWNLOAD_TYPE=dask ``` Benchmarks for an individual workflow can be run from `examples/digital_fingerprinting/production/morpheus` in your container: -``` +```bash +cd examples/digital_fingerprinting/production/morpheus pytest -s --log-level=WARN --benchmark-enable --benchmark-warmup=on --benchmark-warmup-iterations=1 --benchmark-autosave benchmarks/test_bench_e2e_dfp_pipeline.py:: ``` @@ -128,12 +129,12 @@ The `--benchmark-warmup` and `--benchmark-warmup-iterations` options are used to - `test_dfp_stages_duo_inference_e2e` For example, to run E2E benchmarks on the DFP training (modules) workflow on the azure logs: -``` +```bash pytest -s --benchmark-enable --benchmark-warmup=on --benchmark-warmup-iterations=1 --benchmark-autosave benchmarks/test_bench_e2e_dfp_pipeline.py::test_dfp_modules_azure_payload_lti_e2e ``` To run E2E benchmarks on all workflows: -``` +```bash pytest -s --benchmark-enable --benchmark-warmup=on --benchmark-warmup-iterations=1 --benchmark-autosave benchmarks/test_bench_e2e_dfp_pipeline.py ``` diff --git a/examples/digital_fingerprinting/production/morpheus/control_messages/azure_payload_inference.json b/examples/digital_fingerprinting/production/morpheus/control_messages/azure_payload_inference.json index f8d2b9ed3a..713df8f94c 100644 --- a/examples/digital_fingerprinting/production/morpheus/control_messages/azure_payload_inference.json +++ b/examples/digital_fingerprinting/production/morpheus/control_messages/azure_payload_inference.json @@ -1,25 +1,24 @@ { "inputs": [ - { - "tasks": [ - { - "type": "load", - "properties": { - "loader_id": "fsspec", - "files": [ - "../../../data/dfp/azure-inference-data/*.json" - ] + { + "tasks": [ + { + "type": "load", + "properties": { + "loader_id": "fsspec", + "files": [ + "../../data/dfp/azure-inference-data/*.json" + ] + } + }, + { + "type": "inference", + "properties": {} + } + ], + "metadata": { + "data_type": "payload" } - }, - { - "type": "inference", - "properties": { - } - } - ], - "metadata": { - "data_type": "payload" } - } ] - } +} diff --git a/examples/digital_fingerprinting/production/morpheus/control_messages/azure_payload_load_train_inference.json b/examples/digital_fingerprinting/production/morpheus/control_messages/azure_payload_load_train_inference.json index bf0a3771ef..b348eba857 100644 --- a/examples/digital_fingerprinting/production/morpheus/control_messages/azure_payload_load_train_inference.json +++ b/examples/digital_fingerprinting/production/morpheus/control_messages/azure_payload_load_train_inference.json @@ -1,46 +1,44 @@ { "inputs": [ - { - "tasks": [ - { - "type": "load", - "properties": { - "loader_id": "fsspec", - "files": [ - "../../../data/dfp/azure-training-data/*.json" - ] + { + "tasks": [ + { + "type": "load", + "properties": { + "loader_id": "fsspec", + "files": [ + "../../data/dfp/azure-training-data/*.json" + ] + } + }, + { + "type": "training", + "properties": {} + } + ], + "metadata": { + "data_type": "payload" } - }, - { - "type": "training", - "properties": { + }, + { + "tasks": [ + { + "type": "load", + "properties": { + "loader_id": "fsspec", + "files": [ + "../../data/dfp/azure-inference-data/*.json" + ] + } + }, + { + "type": "inference", + "properties": {} + } + ], + "metadata": { + "data_type": "payload" } - } - ], - "metadata": { - "data_type": "payload" } - }, - { - "tasks": [ - { - "type": "load", - "properties": { - "loader_id": "fsspec", - "files": [ - "../../../data/dfp/azure-inference-data/*.json" - ] - } - }, - { - "type": "inference", - "properties": { - } - } - ], - "metadata": { - "data_type": "payload" - } - } ] - } +} diff --git a/examples/digital_fingerprinting/production/morpheus/control_messages/azure_payload_load_training.json b/examples/digital_fingerprinting/production/morpheus/control_messages/azure_payload_load_training.json index dad09e6062..76be4d46a3 100644 --- a/examples/digital_fingerprinting/production/morpheus/control_messages/azure_payload_load_training.json +++ b/examples/digital_fingerprinting/production/morpheus/control_messages/azure_payload_load_training.json @@ -1,25 +1,24 @@ { "inputs": [ - { - "tasks": [ - { - "type": "load", - "properties": { - "loader_id": "fsspec", - "files": [ - "../../../data/dfp/azure-training-data/*.json" - ] + { + "tasks": [ + { + "type": "load", + "properties": { + "loader_id": "fsspec", + "files": [ + "../../data/dfp/azure-training-data/*.json" + ] + } + }, + { + "type": "training", + "properties": {} + } + ], + "metadata": { + "data_type": "payload" } - }, - { - "type": "training", - "properties": { - } - } - ], - "metadata": { - "data_type": "payload" } - } ] - } +} diff --git a/examples/digital_fingerprinting/production/morpheus/control_messages/azure_payload_lti.json b/examples/digital_fingerprinting/production/morpheus/control_messages/azure_payload_lti.json index 1b1e226145..591be771ee 100644 --- a/examples/digital_fingerprinting/production/morpheus/control_messages/azure_payload_lti.json +++ b/examples/digital_fingerprinting/production/morpheus/control_messages/azure_payload_lti.json @@ -1,58 +1,66 @@ { "inputs": [ - { - "tasks": [ - { - "type": "load", - "properties": { - "loader_id": "fsspec", - "files": [ - "../../../data/dfp/azure-training-data/*.json" - ] - } - }, - { - "type": "training", - "properties": { - } - } - ], - "metadata": { - "data_type": "payload", - "batching_options": {"period": "M", "start_time": "2022-08-01", "end_time": "2022-08-31"}, - "file_to_df_options":{ - "parser_kwargs": { - "lines": false, "orient": "records" + { + "tasks": [ + { + "type": "load", + "properties": { + "loader_id": "fsspec", + "files": [ + "../../data/dfp/azure-training-data/*.json" + ] + } + }, + { + "type": "training", + "properties": {} + } + ], + "metadata": { + "data_type": "payload", + "batching_options": { + "period": "M", + "start_time": "2022-08-01", + "end_time": "2022-08-31" + }, + "file_to_df_options": { + "parser_kwargs": { + "lines": false, + "orient": "records" + } } } - } - }, - { - "tasks": [ - { - "type": "load", - "properties": { - "loader_id": "fsspec", - "files": [ - "../../../data/dfp/azure-inference-data/*.json" - ] - } - }, - { - "type": "inference", - "properties": { - } - } - ], - "metadata": { - "data_type": "payload", - "batching_options": {"period": "D", "start_time": "2022-08-01", "end_time": "2022-08-31"}, - "file_to_df_options":{ - "parser_kwargs": { - "lines": false, "orient": "records" + }, + { + "tasks": [ + { + "type": "load", + "properties": { + "loader_id": "fsspec", + "files": [ + "../../data/dfp/azure-inference-data/*.json" + ] + } + }, + { + "type": "inference", + "properties": {} } - } + ], + "metadata": { + "data_type": "payload", + "batching_options": { + "period": "D", + "start_time": "2022-08-01", + "end_time": "2022-08-31" + }, + "file_to_df_options": { + "parser_kwargs": { + "lines": false, + "orient": "records" + } + } + } } - } ] - } +} diff --git a/examples/digital_fingerprinting/production/morpheus/control_messages/azure_payload_training.json b/examples/digital_fingerprinting/production/morpheus/control_messages/azure_payload_training.json index dad09e6062..76be4d46a3 100644 --- a/examples/digital_fingerprinting/production/morpheus/control_messages/azure_payload_training.json +++ b/examples/digital_fingerprinting/production/morpheus/control_messages/azure_payload_training.json @@ -1,25 +1,24 @@ { "inputs": [ - { - "tasks": [ - { - "type": "load", - "properties": { - "loader_id": "fsspec", - "files": [ - "../../../data/dfp/azure-training-data/*.json" - ] + { + "tasks": [ + { + "type": "load", + "properties": { + "loader_id": "fsspec", + "files": [ + "../../data/dfp/azure-training-data/*.json" + ] + } + }, + { + "type": "training", + "properties": {} + } + ], + "metadata": { + "data_type": "payload" } - }, - { - "type": "training", - "properties": { - } - } - ], - "metadata": { - "data_type": "payload" } - } ] - } +} diff --git a/examples/digital_fingerprinting/production/morpheus/control_messages/azure_streaming_inference.json b/examples/digital_fingerprinting/production/morpheus/control_messages/azure_streaming_inference.json index 9c5d889d5c..80fd16077e 100644 --- a/examples/digital_fingerprinting/production/morpheus/control_messages/azure_streaming_inference.json +++ b/examples/digital_fingerprinting/production/morpheus/control_messages/azure_streaming_inference.json @@ -1,25 +1,24 @@ { "inputs": [ - { - "tasks": [ - { - "type": "load", - "properties": { - "loader_id": "fsspec", - "files": [ - "../../../data/dfp/azure-inference-data/*.json" - ] + { + "tasks": [ + { + "type": "load", + "properties": { + "loader_id": "fsspec", + "files": [ + "../../data/dfp/azure-inference-data/*.json" + ] + } + }, + { + "type": "inference", + "properties": {} + } + ], + "metadata": { + "data_type": "streaming" } - }, - { - "type": "inference", - "properties": { - } - } - ], - "metadata": { - "data_type": "streaming" } - } ] - } +} diff --git a/examples/digital_fingerprinting/production/morpheus/control_messages/azure_streaming_lti.json b/examples/digital_fingerprinting/production/morpheus/control_messages/azure_streaming_lti.json index 7a28c85d73..0a34d21c8e 100644 --- a/examples/digital_fingerprinting/production/morpheus/control_messages/azure_streaming_lti.json +++ b/examples/digital_fingerprinting/production/morpheus/control_messages/azure_streaming_lti.json @@ -1,58 +1,66 @@ { "inputs": [ - { - "tasks": [ - { - "type": "load", - "properties": { - "loader_id": "fsspec", - "files": [ - "../../../data/dfp/azure-training-data/*.json" - ] - } - }, - { - "type": "training", - "properties": { - } - } - ], - "metadata": { - "data_type": "streaming", - "batching_options": {"period": "W", "start_time": "2022-08-01", "end_time": "2022-08-31"}, - "file_to_df_options":{ - "parser_kwargs": { - "lines": false, "orient": "records" + { + "tasks": [ + { + "type": "load", + "properties": { + "loader_id": "fsspec", + "files": [ + "../../data/dfp/azure-training-data/*.json" + ] + } + }, + { + "type": "training", + "properties": {} + } + ], + "metadata": { + "data_type": "streaming", + "batching_options": { + "period": "W", + "start_time": "2022-08-01", + "end_time": "2022-08-31" + }, + "file_to_df_options": { + "parser_kwargs": { + "lines": false, + "orient": "records" + } } } - } - }, - { - "tasks": [ - { - "type": "load", - "properties": { - "loader_id": "fsspec", - "files": [ - "../../../data/dfp/azure-inference-data/*.json" - ] - } - }, - { - "type": "inference", - "properties": { - } - } - ], - "metadata": { - "data_type": "streaming", - "batching_options": {"period": "H", "start_time": "2022-08-01", "end_time": "2022-08-31"}, - "file_to_df_options":{ - "parser_kwargs": { - "lines": false, "orient": "records" + }, + { + "tasks": [ + { + "type": "load", + "properties": { + "loader_id": "fsspec", + "files": [ + "../../data/dfp/azure-inference-data/*.json" + ] + } + }, + { + "type": "inference", + "properties": {} } - } + ], + "metadata": { + "data_type": "streaming", + "batching_options": { + "period": "H", + "start_time": "2022-08-01", + "end_time": "2022-08-31" + }, + "file_to_df_options": { + "parser_kwargs": { + "lines": false, + "orient": "records" + } + } + } } - } ] - } +} diff --git a/examples/digital_fingerprinting/production/morpheus/control_messages/azure_streaming_training.json b/examples/digital_fingerprinting/production/morpheus/control_messages/azure_streaming_training.json index 882b23a0a3..bf2a6796b9 100644 --- a/examples/digital_fingerprinting/production/morpheus/control_messages/azure_streaming_training.json +++ b/examples/digital_fingerprinting/production/morpheus/control_messages/azure_streaming_training.json @@ -1,25 +1,24 @@ { "inputs": [ - { - "tasks": [ - { - "type": "load", - "properties": { - "loader_id": "fsspec", - "files": [ - "../../../data/dfp/azure-training-data/*.json" - ] + { + "tasks": [ + { + "type": "load", + "properties": { + "loader_id": "fsspec", + "files": [ + "../../data/dfp/azure-training-data/*.json" + ] + } + }, + { + "type": "training", + "properties": {} + } + ], + "metadata": { + "data_type": "streaming" } - }, - { - "type": "training", - "properties": { - } - } - ], - "metadata": { - "data_type": "streaming" } - } ] - } +} diff --git a/examples/digital_fingerprinting/production/morpheus/control_messages/duo_payload_inference.json b/examples/digital_fingerprinting/production/morpheus/control_messages/duo_payload_inference.json index ebd6669be7..ca845d802c 100644 --- a/examples/digital_fingerprinting/production/morpheus/control_messages/duo_payload_inference.json +++ b/examples/digital_fingerprinting/production/morpheus/control_messages/duo_payload_inference.json @@ -1,25 +1,24 @@ { "inputs": [ - { - "tasks": [ - { - "type": "load", - "properties": { - "loader_id": "fsspec", - "files": [ - "../../../data/dfp/duo-inference-data/*.json" - ] + { + "tasks": [ + { + "type": "load", + "properties": { + "loader_id": "fsspec", + "files": [ + "../../data/dfp/duo-inference-data/*.json" + ] + } + }, + { + "type": "inference", + "properties": {} + } + ], + "metadata": { + "data_type": "payload" } - }, - { - "type": "inference", - "properties": { - } - } - ], - "metadata": { - "data_type": "payload" } - } ] - } +} diff --git a/examples/digital_fingerprinting/production/morpheus/control_messages/duo_payload_load_train_inference.json b/examples/digital_fingerprinting/production/morpheus/control_messages/duo_payload_load_train_inference.json index e30f66ccfe..cc1efb8373 100644 --- a/examples/digital_fingerprinting/production/morpheus/control_messages/duo_payload_load_train_inference.json +++ b/examples/digital_fingerprinting/production/morpheus/control_messages/duo_payload_load_train_inference.json @@ -1,46 +1,44 @@ { "inputs": [ - { - "tasks": [ - { - "type": "load", - "properties": { - "loader_id": "fsspec", - "files": [ - "../../../data/dfp/duo-training-data/*.json" - ] + { + "tasks": [ + { + "type": "load", + "properties": { + "loader_id": "fsspec", + "files": [ + "../../data/dfp/duo-training-data/*.json" + ] + } + }, + { + "type": "training", + "properties": {} + } + ], + "metadata": { + "data_type": "payload" } - }, - { - "type": "training", - "properties": { + }, + { + "tasks": [ + { + "type": "load", + "properties": { + "loader_id": "fsspec", + "files": [ + "../../data/dfp/duo-inference-data/*.json" + ] + } + }, + { + "type": "inference", + "properties": {} + } + ], + "metadata": { + "data_type": "payload" } - } - ], - "metadata": { - "data_type": "payload" } - }, - { - "tasks": [ - { - "type": "load", - "properties": { - "loader_id": "fsspec", - "files": [ - "../../../data/dfp/duo-inference-data/*.json" - ] - } - }, - { - "type": "inference", - "properties": { - } - } - ], - "metadata": { - "data_type": "payload" - } - } ] - } +} diff --git a/examples/digital_fingerprinting/production/morpheus/control_messages/duo_payload_lti.json b/examples/digital_fingerprinting/production/morpheus/control_messages/duo_payload_lti.json index 382ff22808..0c42eb97c1 100644 --- a/examples/digital_fingerprinting/production/morpheus/control_messages/duo_payload_lti.json +++ b/examples/digital_fingerprinting/production/morpheus/control_messages/duo_payload_lti.json @@ -1,58 +1,66 @@ { "inputs": [ - { - "tasks": [ - { - "type": "load", - "properties": { - "loader_id": "fsspec", - "files": [ - "../../../data/dfp/duo-training-data/*.json" - ] - } - }, - { - "type": "training", - "properties": { - } - } - ], - "metadata": { - "data_type": "payload", - "batching_options": {"period": "M", "start_time": "2022-08-01", "end_time": "2022-08-31"}, - "file_to_df_options":{ - "parser_kwargs": { - "lines": false, "orient": "records" + { + "tasks": [ + { + "type": "load", + "properties": { + "loader_id": "fsspec", + "files": [ + "../../data/dfp/duo-training-data/*.json" + ] + } + }, + { + "type": "training", + "properties": {} + } + ], + "metadata": { + "data_type": "payload", + "batching_options": { + "period": "M", + "start_time": "2022-08-01", + "end_time": "2022-08-31" + }, + "file_to_df_options": { + "parser_kwargs": { + "lines": false, + "orient": "records" + } } } - } - }, - { - "tasks": [ - { - "type": "load", - "properties": { - "loader_id": "fsspec", - "files": [ - "../../../data/dfp/duo-inference-data/*.json" - ] - } - }, - { - "type": "inference", - "properties": { - } - } - ], - "metadata": { - "data_type": "payload", - "batching_options": {"period": "T", "start_time": "2022-08-01", "end_time": "2022-08-31"}, - "file_to_df_options":{ - "parser_kwargs": { - "lines": false, "orient": "records" + }, + { + "tasks": [ + { + "type": "load", + "properties": { + "loader_id": "fsspec", + "files": [ + "../../data/dfp/duo-inference-data/*.json" + ] + } + }, + { + "type": "inference", + "properties": {} + } + ], + "metadata": { + "data_type": "payload", + "batching_options": { + "period": "T", + "start_time": "2022-08-01", + "end_time": "2022-08-31" + }, + "file_to_df_options": { + "parser_kwargs": { + "lines": false, + "orient": "records" + } } } } - } ] - } +} diff --git a/examples/digital_fingerprinting/production/morpheus/control_messages/duo_payload_only_load.json b/examples/digital_fingerprinting/production/morpheus/control_messages/duo_payload_only_load.json index ed69cbf5af..c166abdcd9 100644 --- a/examples/digital_fingerprinting/production/morpheus/control_messages/duo_payload_only_load.json +++ b/examples/digital_fingerprinting/production/morpheus/control_messages/duo_payload_only_load.json @@ -1,20 +1,20 @@ { "inputs": [ - { - "tasks": [ - { - "type": "load", - "properties": { - "loader_id": "fsspec", - "files": [ - "../../../data/dfp/duo-inference-data/*.json" - ] + { + "tasks": [ + { + "type": "load", + "properties": { + "loader_id": "fsspec", + "files": [ + "../../data/dfp/duo-inference-data/*.json" + ] + } + } + ], + "metadata": { + "data_type": "payload" } - } - ], - "metadata": { - "data_type": "payload" } - } ] - } +} diff --git a/examples/digital_fingerprinting/production/morpheus/control_messages/duo_payload_training.json b/examples/digital_fingerprinting/production/morpheus/control_messages/duo_payload_training.json index 2b9995875f..8016afee90 100644 --- a/examples/digital_fingerprinting/production/morpheus/control_messages/duo_payload_training.json +++ b/examples/digital_fingerprinting/production/morpheus/control_messages/duo_payload_training.json @@ -7,14 +7,13 @@ "properties": { "loader_id": "fsspec", "files": [ - "../../../data/dfp/duo-training-data/*.json" + "../../data/dfp/duo-training-data/*.json" ] } }, { "type": "training", - "properties": { - } + "properties": {} } ], "metadata": { diff --git a/examples/digital_fingerprinting/production/morpheus/control_messages/duo_streaming_inference.json b/examples/digital_fingerprinting/production/morpheus/control_messages/duo_streaming_inference.json index 2b033b9d7c..dce50b274e 100644 --- a/examples/digital_fingerprinting/production/morpheus/control_messages/duo_streaming_inference.json +++ b/examples/digital_fingerprinting/production/morpheus/control_messages/duo_streaming_inference.json @@ -1,25 +1,24 @@ { "inputs": [ - { - "tasks": [ - { - "type": "load", - "properties": { - "loader_id": "fsspec", - "files": [ - "../../../data/dfp/duo-inference-data/*.json" - ] + { + "tasks": [ + { + "type": "load", + "properties": { + "loader_id": "fsspec", + "files": [ + "../../data/dfp/duo-inference-data/*.json" + ] + } + }, + { + "type": "inference", + "properties": {} + } + ], + "metadata": { + "data_type": "streaming" } - }, - { - "type": "inference", - "properties": { - } - } - ], - "metadata": { - "data_type": "streaming" } - } ] - } +} diff --git a/examples/digital_fingerprinting/production/morpheus/control_messages/duo_streaming_lti.json b/examples/digital_fingerprinting/production/morpheus/control_messages/duo_streaming_lti.json index d74bae952b..6b497b1ba5 100644 --- a/examples/digital_fingerprinting/production/morpheus/control_messages/duo_streaming_lti.json +++ b/examples/digital_fingerprinting/production/morpheus/control_messages/duo_streaming_lti.json @@ -1,58 +1,66 @@ { "inputs": [ - { - "tasks": [ - { - "type": "load", - "properties": { - "loader_id": "fsspec", - "files": [ - "../../../data/dfp/duo-training-data/*.json" - ] - } - }, - { - "type": "training", - "properties": { - } - } - ], - "metadata": { - "data_type": "streaming", - "batching_options": {"period": "Y", "start_time": "2022-08-01", "end_time": "2022-08-31"}, - "file_to_df_options":{ - "parser_kwargs": { - "lines": false, "orient": "records" + { + "tasks": [ + { + "type": "load", + "properties": { + "loader_id": "fsspec", + "files": [ + "../../data/dfp/duo-training-data/*.json" + ] + } + }, + { + "type": "training", + "properties": {} + } + ], + "metadata": { + "data_type": "streaming", + "batching_options": { + "period": "Y", + "start_time": "2022-08-01", + "end_time": "2022-08-31" + }, + "file_to_df_options": { + "parser_kwargs": { + "lines": false, + "orient": "records" + } } } - } - }, - { - "tasks": [ - { - "type": "load", - "properties": { - "loader_id": "fsspec", - "files": [ - "../../../data/dfp/duo-inference-data/*.json" - ] - } - }, - { - "type": "inference", - "properties": { - } - } - ], - "metadata": { - "data_type": "streaming", - "batching_options": {"period": "T", "start_time": "2022-08-01", "end_time": "2022-09-01"}, - "file_to_df_options":{ - "parser_kwargs": { - "lines": false, "orient": "records" + }, + { + "tasks": [ + { + "type": "load", + "properties": { + "loader_id": "fsspec", + "files": [ + "../../data/dfp/duo-inference-data/*.json" + ] + } + }, + { + "type": "inference", + "properties": {} + } + ], + "metadata": { + "data_type": "streaming", + "batching_options": { + "period": "T", + "start_time": "2022-08-01", + "end_time": "2022-09-01" + }, + "file_to_df_options": { + "parser_kwargs": { + "lines": false, + "orient": "records" + } } } } - } ] - } +} diff --git a/examples/digital_fingerprinting/production/morpheus/control_messages/duo_streaming_only_load.json b/examples/digital_fingerprinting/production/morpheus/control_messages/duo_streaming_only_load.json index 205842d72b..938f7603ff 100644 --- a/examples/digital_fingerprinting/production/morpheus/control_messages/duo_streaming_only_load.json +++ b/examples/digital_fingerprinting/production/morpheus/control_messages/duo_streaming_only_load.json @@ -1,20 +1,20 @@ { "inputs": [ - { - "tasks": [ - { - "type": "load", - "properties": { - "loader_id": "fsspec", - "files": [ - "../../../data/dfp/duo-inference-data/*.json" - ] + { + "tasks": [ + { + "type": "load", + "properties": { + "loader_id": "fsspec", + "files": [ + "../../data/dfp/duo-inference-data/*.json" + ] + } + } + ], + "metadata": { + "data_type": "streaming" } - } - ], - "metadata": { - "data_type": "streaming" } - } ] - } +} diff --git a/examples/digital_fingerprinting/production/morpheus/control_messages/duo_streaming_payload.json b/examples/digital_fingerprinting/production/morpheus/control_messages/duo_streaming_payload.json index 3daf318961..0faf638b5c 100644 --- a/examples/digital_fingerprinting/production/morpheus/control_messages/duo_streaming_payload.json +++ b/examples/digital_fingerprinting/production/morpheus/control_messages/duo_streaming_payload.json @@ -1,46 +1,44 @@ { "inputs": [ - { - "tasks": [ - { - "type": "load", - "properties": { - "loader_id": "fsspec", - "files": [ - "../../../data/dfp/duo-training-data/*.json" - ] + { + "tasks": [ + { + "type": "load", + "properties": { + "loader_id": "fsspec", + "files": [ + "../../data/dfp/duo-training-data/*.json" + ] + } + }, + { + "type": "training", + "properties": {} + } + ], + "metadata": { + "data_type": "streaming" } - }, - { - "type": "training", - "properties": { + }, + { + "tasks": [ + { + "type": "load", + "properties": { + "loader_id": "fsspec", + "files": [ + "../../data/dfp/duo-inference-data/*.json" + ] + } + }, + { + "type": "inference", + "properties": {} + } + ], + "metadata": { + "data_type": "payload" } - } - ], - "metadata": { - "data_type": "streaming" } - }, - { - "tasks": [ - { - "type": "load", - "properties": { - "loader_id": "fsspec", - "files": [ - "../../../data/dfp/duo-inference-data/*.json" - ] - } - }, - { - "type": "inference", - "properties": { - } - } - ], - "metadata": { - "data_type": "payload" - } - } ] - } +} diff --git a/examples/digital_fingerprinting/production/morpheus/control_messages/duo_streaming_training.json b/examples/digital_fingerprinting/production/morpheus/control_messages/duo_streaming_training.json index 4486149127..fe6e41b0bd 100644 --- a/examples/digital_fingerprinting/production/morpheus/control_messages/duo_streaming_training.json +++ b/examples/digital_fingerprinting/production/morpheus/control_messages/duo_streaming_training.json @@ -7,14 +7,13 @@ "properties": { "loader_id": "fsspec", "files": [ - "../../../data/dfp/duo-training-data/*.json" + "../../data/dfp/duo-training-data/*.json" ] } }, { "type": "training", - "properties": { - } + "properties": {} } ], "metadata": { diff --git a/examples/digital_fingerprinting/visualization/README.md b/examples/digital_fingerprinting/visualization/README.md index 9d6542e2e7..b804c07637 100644 --- a/examples/digital_fingerprinting/visualization/README.md +++ b/examples/digital_fingerprinting/visualization/README.md @@ -63,7 +63,7 @@ http://localhost:5000 Run the following in your `morpheus_pipeline` container to download example data from S3: -``` +```bash /workspace/examples/digital_fingerprinting/fetch_example_data.py all ``` @@ -81,18 +81,18 @@ The pipeline uses `DFPVizPostprocStage` to perform post-processing on DFP infere ``` Set `PYTHONPATH` environment variable to allow import of production DFP Morpheus stages: -``` +```bash export PYTHONPATH=/workspace/examples/digital_fingerprinting/production/morpheus ``` ### Azure -``` +```bash cd /workspace/examples/digital_fingerprinting/visualization ``` Train DFP user models using Azure log files in `/workspace/examples/data/dfp/azure-training-data` and save them to MLflow. -``` +```bash python dfp_viz_azure_pipeline.py \ --train_users=all \ --log_level=debug \ @@ -102,7 +102,7 @@ python dfp_viz_azure_pipeline.py \ **Note:** Since models are persisted to a Docker volume, the above command only needs to be run once even if the `mlflow` service is restarted. Run inference with DFP viz post-processing using Azure log files in `/workspace/examples/data/dfp/azure-inference-data` to generate input files for Azure DFP visualization: -``` +```bash python dfp_viz_azure_pipeline.py \ --train_users=none \ --log_level=debug \ @@ -116,7 +116,7 @@ When the pipeline run completes, the `dfp-viz-azure-2022-08-30.csv` and `dfp-viz ### Duo Train: -``` +```bash python dfp_viz_duo_pipeline.py \ --train_users=all \ --log_level=debug \ @@ -124,7 +124,7 @@ python dfp_viz_duo_pipeline.py \ --input_file=/workspace/examples/data/dfp/duo-training-data/DUO_2022-08-*.json ``` Inference: -``` +```bash python dfp_viz_duo_pipeline.py \ --train_users=none \ --log_level=debug \ @@ -139,28 +139,24 @@ While still in the `morpheus_pipeline` container, perform the following steps to ### Install dependencies -``` +```bash cd viz -``` -``` corepack enable -``` -``` yarn ``` ### Configure `dataset_path` Set the `dataset_path` environment variable to directory where viz input files will be read from. For this example, we'll set it to directory that contains our Azure DFP output files: -``` +```bash export dataset_path=/workspace/examples/digital_fingerprinting/visualization/azure-dfp-output ``` ### Start server -``` +```bash yarn dev ``` -The DFP Visualization Tool can now be accessed via web browser at http://localhost:3000. +The DFP Visualization Tool can now be accessed via web browser at http://localhost:3000 . diff --git a/examples/gnn_fraud_detection_pipeline/run.py b/examples/gnn_fraud_detection_pipeline/run.py index 27361d05ea..a5de019ed7 100644 --- a/examples/gnn_fraud_detection_pipeline/run.py +++ b/examples/gnn_fraud_detection_pipeline/run.py @@ -16,6 +16,10 @@ import os import click +# pylint: disable=no-name-in-module +from stages.classification_stage import ClassificationStage +from stages.graph_construction_stage import FraudGraphConstructionStage +from stages.graph_sage_stage import GraphSAGEStage from morpheus.config import Config from morpheus.config import PipelineModes @@ -27,11 +31,6 @@ from morpheus.stages.preprocess.deserialize_stage import DeserializeStage from morpheus.utils.logger import configure_logging -# pylint: disable=no-name-in-module -from .stages.classification_stage import ClassificationStage -from .stages.graph_construction_stage import FraudGraphConstructionStage -from .stages.graph_sage_stage import GraphSAGEStage - CUR_DIR = os.path.dirname(__file__) diff --git a/examples/llm/agents/README.md b/examples/llm/agents/README.md index bb5c1d9bc3..ef7cfcb644 100644 --- a/examples/llm/agents/README.md +++ b/examples/llm/agents/README.md @@ -126,10 +126,13 @@ This example demonstrates the basic implementation of Morpheus pipeline, showcas - Stores and manages the results within the pipeline using an InMemorySinkStage. +To run the example with default options, use the following command: ```bash -python examples/llm/main.py agents simple [OPTIONS] +python examples/llm/main.py --log_level=info agents simple ``` +Available options for the simple pipeline are as follows: + ### Options: - `--use_cpu_only` - **Description**: Run in CPU only mode diff --git a/examples/llm/completion/README.md b/examples/llm/completion/README.md index 562e1a1020..92790caa52 100644 --- a/examples/llm/completion/README.md +++ b/examples/llm/completion/README.md @@ -145,6 +145,5 @@ python examples/llm/main.py completion [OPTIONS] COMMAND [ARGS]... ### Running Morpheus Pipeline with OpenAI LLM service ```bash - python examples/llm/main.py completion pipeline --llm_service OpenAI ``` diff --git a/examples/llm/rag/README.md b/examples/llm/rag/README.md index f7c0863b5e..e2087fa129 100644 --- a/examples/llm/rag/README.md +++ b/examples/llm/rag/README.md @@ -96,14 +96,6 @@ The standalone Morpheus pipeline is built using the following components: Before running the pipeline, we need obtain service API keys for the following services: -### Ensure that LFS files are downloaded - -To retrieve datasets from LFS run the following: - -```bash -./scripts/fetch_data.py fetch datasets -``` - ### Obtain an OpenAI API or NGC API Key #### NGC diff --git a/examples/llm/vdb_upload/README.md b/examples/llm/vdb_upload/README.md index 0a6e19b5d1..2f96589ebe 100644 --- a/examples/llm/vdb_upload/README.md +++ b/examples/llm/vdb_upload/README.md @@ -123,16 +123,21 @@ Before running the pipeline, we need to ensure that the following services are r #### Ensure LFS files are downloaded -To retrieve datasets from LFS run the following: +To retrieve the datasets from LFS run the following: ```bash -./scripts/fetch_data.py fetch datasets +./scripts/fetch_data.py fetch examples ``` #### Milvus Service -- Follow the instructions [here](https://milvus.io/docs/install_standalone-docker.md) to install and run a Milvus - service. +- Refer to the [Milvus documentation](https://milvus.io/docs/install-overview.md) for more details on running Milvus. For the purposes of testing the pipeline, we will launch an instance of Milvus Lite. + +From the root of the Morpheus repository, run the following to launch Milvus in a separate terminal: +```bash +mkdir -p .tmp/milvusdb +milvus-server --data .tmp/milvusdb +``` #### Triton Service diff --git a/examples/log_parsing/README.md b/examples/log_parsing/README.md index d712d619c4..5d2485a3bc 100644 --- a/examples/log_parsing/README.md +++ b/examples/log_parsing/README.md @@ -63,11 +63,11 @@ Run the following from the root of the Morpheus repo to start the log parsing pi ```bash python examples/log_parsing/run.py \ - --input_file=./models/datasets/validation-data/log-parsing-validation-data-input.csv \ + --input_file=./examples/data/log-parsing-validation-data-input.csv \ --model_vocab_hash_file=data/bert-base-cased-hash.txt \ - --model_vocab_file=./models/training-tuning-scripts/sid-models/resources/bert-base-cased-vocab.txt \ + --model_vocab_file=data/bert-base-cased-vocab.txt \ --model_name log-parsing-onnx \ - --model_config_file=./models/log-parsing-models/log-parsing-config-20220418.json + --model_config_file=./examples/data/log-parsing-config-20220418.json ``` Use `--help` to display information about the command line options: diff --git a/examples/log_parsing/inference.py b/examples/log_parsing/inference.py index 099928cff9..c815389e5f 100644 --- a/examples/log_parsing/inference.py +++ b/examples/log_parsing/inference.py @@ -140,7 +140,8 @@ def compute_schema(self, schema: StageSchema): schema.output_schema.set_type(ControlMessage) @staticmethod - def _convert_one_response(output: ControlMessage, inf: ControlMessage, res: TensorMemory) -> ControlMessage: + def _convert_one_response(output: ControlMessage, inf: ControlMessage, res: TensorMemory, + batch_offset: int) -> ControlMessage: memory = output.tensors() out_seq_ids = memory.get_tensor('seq_ids') @@ -153,8 +154,8 @@ def _convert_one_response(output: ControlMessage, inf: ControlMessage, res: Tens seq_offset = seq_ids[0, 0].item() seq_count = seq_ids[-1, 0].item() + 1 - seq_offset - input_ids[0:inf.tensors().count, :] = inf.tensors().get_tensor('input_ids') - out_seq_ids[0:inf.tensors().count, :] = seq_ids + input_ids[batch_offset:inf.tensors().count + batch_offset, :] = inf.tensors().get_tensor('input_ids') + out_seq_ids[batch_offset:inf.tensors().count + batch_offset, :] = seq_ids resp_confidences = res.get_tensor('confidences') resp_labels = res.get_tensor('labels') @@ -162,8 +163,8 @@ def _convert_one_response(output: ControlMessage, inf: ControlMessage, res: Tens # Two scenarios: if (inf.payload().count == inf.tensors().count): assert seq_count == res.count - confidences[0:inf.tensors().count, :] = resp_confidences - labels[0:inf.tensors().count, :] = resp_labels + confidences[batch_offset:inf.tensors().count + batch_offset, :] = resp_confidences + labels[batch_offset:inf.tensors().count + batch_offset, :] = resp_labels else: assert inf.tensors().count == res.count diff --git a/examples/log_parsing/postprocessing.py b/examples/log_parsing/postprocessing.py index f19836d896..1e4d89689c 100644 --- a/examples/log_parsing/postprocessing.py +++ b/examples/log_parsing/postprocessing.py @@ -83,8 +83,12 @@ def compute_schema(self, schema: StageSchema): schema.output_schema.set_type(MessageMeta) def _postprocess(self, msg: ControlMessage): - infer_pdf = pd.DataFrame(msg.tensors().get_tensor('seq_ids').get()).astype(int) - infer_pdf.columns = ["doc", "start", "stop"] + with msg.payload().mutable_dataframe() as src_df: + src_index = src_df.index.to_pandas() + + seq_ids = msg.tensors().get_tensor('seq_ids').get() + infer_pdf = pd.DataFrame({"doc": src_index, "start": seq_ids[:, 1], "stop": seq_ids[:, 2]}) + infer_pdf["confidences"] = msg.tensors().get_tensor('confidences').tolist() infer_pdf["labels"] = msg.tensors().get_tensor('labels').tolist() infer_pdf["token_ids"] = msg.tensors().get_tensor('input_ids').tolist() diff --git a/examples/log_parsing/run.py b/examples/log_parsing/run.py index d4879f4c55..a85379f166 100644 --- a/examples/log_parsing/run.py +++ b/examples/log_parsing/run.py @@ -69,7 +69,7 @@ help="Model vocab hash file to use for pre-processing.") @click.option('--model_vocab_file', required=True, - type=click.Path(exists=True, dir_okay=False), + type=MorpheusRelativePath(exists=True, dir_okay=False), help="Model vocab file to use for post-processing.") @click.option("--model_seq_length", default=256, diff --git a/examples/nlp_si_detection/README.md b/examples/nlp_si_detection/README.md index e13f719087..37d4abfa1f 100644 --- a/examples/nlp_si_detection/README.md +++ b/examples/nlp_si_detection/README.md @@ -117,7 +117,7 @@ morpheus --log_level=DEBUG \ pipeline-nlp --model_seq_length=256 \ `# 1st Stage: Read from file` \ from-file --filename=examples/data/pcap_dump.jsonlines \ - `# 2nd Stage: Deserialize from JSON strings to objects` \ + `# 2nd Stage: Deserialize batch DataFrame into ControlMessages` \ deserialize \ `# 3rd Stage: Preprocessing converts the input data into BERT tokens` \ preprocess --vocab_hash_file=data/bert-base-uncased-hash.txt --do_lower_case=True --truncation=True \ diff --git a/examples/ransomware_detection/README.md b/examples/ransomware_detection/README.md index a0e16a3311..e1f7197e1e 100644 --- a/examples/ransomware_detection/README.md +++ b/examples/ransomware_detection/README.md @@ -121,3 +121,5 @@ Options: output will be saved. --help Show this message and exit. ``` + +> **Note**: There is a known race condition in `dask.distributed` which occasionally causes `tornado.iostream.StreamClosedError` to be raised during shutdown, but does not affect the output of the pipeline. If you see this exception during shutdown, it is typically safe to ignore unless it corresponds to other undesirable behavior. For more information see ([#2026](https://github.com/nv-morpheus/Morpheus/issues/2026)). diff --git a/examples/ransomware_detection/run.py b/examples/ransomware_detection/run.py index a94fe301f6..4887e7ff1b 100644 --- a/examples/ransomware_detection/run.py +++ b/examples/ransomware_detection/run.py @@ -18,6 +18,8 @@ import click import yaml +from stages.create_features import CreateFeaturesRWStage +from stages.preprocessing import PreprocessingRWStage from morpheus.config import Config from morpheus.config import PipelineModes @@ -30,9 +32,6 @@ from morpheus.stages.postprocess.serialize_stage import SerializeStage from morpheus.utils.logger import configure_logging -from .stages.create_features import CreateFeaturesRWStage -from .stages.preprocessing import PreprocessingRWStage - CUR_DIR = os.path.dirname(__file__) diff --git a/examples/root_cause_analysis/README.md b/examples/root_cause_analysis/README.md index d7a5f9d94a..45d36b8f0f 100644 --- a/examples/root_cause_analysis/README.md +++ b/examples/root_cause_analysis/README.md @@ -110,8 +110,8 @@ run --num_threads=8 --edge_buffer_size=4 --pipeline_batch_size=1024 --model_max_ `# Specify a NLP pipeline with 128 sequence length (Must match Triton config)` \ pipeline-nlp --model_seq_length=128 --label=not_root_cause --label=is_root_cause \ `# 1st Stage: Read from file` \ -from-file --filename=${MORPHEUS_ROOT}/models/datasets/validation-data/root-cause-validation-data-input.jsonlines \ -`# 2nd Stage: Deserialize from JSON strings to objects` \ +from-file --filename=${MORPHEUS_ROOT}/examples/data/root-cause-validation-data-input.jsonlines \ +`# 2nd Stage: Deserialize batch DataFrame into ControlMessages` \ deserialize \ `# 3rd Stage: Preprocessing converts the input data into BERT tokens` \ preprocess --column=log --vocab_hash_file=./data/bert-base-uncased-hash.txt --truncation=True --do_lower_case=True --add_special_tokens=False \ diff --git a/external/utilities b/external/utilities index 3ae1a80875..7f5904513c 160000 --- a/external/utilities +++ b/external/utilities @@ -1 +1 @@ -Subproject commit 3ae1a808759c8d57ee74d5c7038e0fbdb437de38 +Subproject commit 7f5904513ca1281670aea8c351dae140892d3dfc diff --git a/models/README.md b/models/README.md index 39a9a4aa47..0dd6fe17ba 100644 --- a/models/README.md +++ b/models/README.md @@ -73,6 +73,16 @@ This model is an example of customized transformer-based sensitive information d English text from PCAP payloads #### Output Multi-label sequence classification for 10 sensitive information categories +### Generating TRT Models from ONNX +The ONNX to TensorRT conversion utility requires additional packages, which can be installed using the following command: +```bash +conda env update --solver=libmamba -n morpheus --file conda/environments/model-utils_cuda-125_arch-x86_64.yaml +``` +For the best performance you need to compile a TensorRT engine file on each machine that it will be run on. To facilitate this, Morpheus contains a utility to input an ONNX file and export the TensorRT engine file. Sample command to generate the TensorRT engine file - +```bash +morpheus --log_level=info tools onnx-to-trt --input_model sid-models/sid-minibert-20230424.onnx --output_model ./model.plan --batches 1 8 --batches 1 16 --batches 1 32 --seq_length 256 --max_workspace_size 16000 +``` +Note: If you get an out-of-memory error, reduce the `--max_workspace_size` argument until it will successfully run. ### References Well-Read Students Learn Better: On the Importance of Pre-training Compact Models, 2019, https://arxiv.org/abs/1908.08962 @@ -89,6 +99,11 @@ This model is an example of customized transformer-based phishing email detectio Entire email as a string #### Output Binary sequence classification as phishing/spam or non-phishing/spam +### Generating TRT Models from ONNX +For the best performance you need to compile a TensorRT engine file on each machine that it will be run on. To facilitate this, Morpheus contains a utility to input an ONNX file and export the TensorRT engine file. Sample command to generate the TensorRT engine file - +```bash +morpheus --log_level=info tools onnx-to-trt --input_model phishing-models/phishing-bert-20230517.onnx --output_model ./model.plan --batches 1 8 --batches 1 16 --batches 1 32 --seq_length 256 --max_workspace_size 16000 +``` ### References - https://archive.ics.uci.edu/ml/datasets/SMS+Spam+Collection - Devlin J. et al. (2018), BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding diff --git a/models/docker/Dockerfile b/models/docker/Dockerfile index b2df3fc1b1..27123457a3 100644 --- a/models/docker/Dockerfile +++ b/models/docker/Dockerfile @@ -14,7 +14,7 @@ # limitations under the License. ARG FROM_IMAGE="nvcr.io/nvidia/tritonserver" -ARG FROM_IMAGE_TAG="23.06-py3" +ARG FROM_IMAGE_TAG="24.09-py3" FROM ${FROM_IMAGE}:${FROM_IMAGE_TAG} AS base WORKDIR / diff --git a/models/docker/build_container.sh b/models/docker/build_container.sh index 8cdeba8b71..c2667969a8 100755 --- a/models/docker/build_container.sh +++ b/models/docker/build_container.sh @@ -31,7 +31,7 @@ SHORT_VERSION=${MAJOR_VERSION}.${MINOR_VERSION} # Build args FROM_IMAGE=${FROM_IMAGE:-"nvcr.io/nvidia/tritonserver"} -FROM_IMAGE_TAG=${FROM_IMAGE_TAG:-"23.06-py3"} +FROM_IMAGE_TAG=${FROM_IMAGE_TAG:-"24.09-py3"} DOCKER_IMAGE_NAME=${DOCKER_IMAGE_NAME:-"nvcr.io/nvidia/morpheus/morpheus-tritonserver-models"} DOCKER_IMAGE_TAG=${DOCKER_IMAGE_TAG:-"${SHORT_VERSION}"} diff --git a/models/ransomware-models/ransomw-model-long-rf/checkpoint.tl b/models/ransomware-models/ransomw-model-long-rf/checkpoint.tl index 62b5cce607..6be0593905 100644 Binary files a/models/ransomware-models/ransomw-model-long-rf/checkpoint.tl and b/models/ransomware-models/ransomw-model-long-rf/checkpoint.tl differ diff --git a/models/ransomware-models/ransomw-model-medium-rf/checkpoint.tl b/models/ransomware-models/ransomw-model-medium-rf/checkpoint.tl index 24afedf5d1..cab73f763b 100644 Binary files a/models/ransomware-models/ransomw-model-medium-rf/checkpoint.tl and b/models/ransomware-models/ransomw-model-medium-rf/checkpoint.tl differ diff --git a/models/ransomware-models/ransomw-model-short-rf/checkpoint.tl b/models/ransomware-models/ransomw-model-short-rf/checkpoint.tl index 2ba7690f52..b2a2875ce3 100644 Binary files a/models/ransomware-models/ransomw-model-short-rf/checkpoint.tl and b/models/ransomware-models/ransomw-model-short-rf/checkpoint.tl differ diff --git a/models/triton-model-repo/README.md b/models/triton-model-repo/README.md index 790bef475e..c12a0fb828 100644 --- a/models/triton-model-repo/README.md +++ b/models/triton-model-repo/README.md @@ -71,11 +71,15 @@ docker run --rm --gpus=all -p 8000:8000 -p 8001:8001 -p 8002:8002 -v $PWD:/model ### Load `sid-minibert-trt` Model with Default Triton Image from Morpheus Repo -To load a TensorRT model, it first must be compiled with the `morpheus tools onnx-to-trt` utility (See `triton-model-repo/sid-minibert-trt/1/README.md` for more info): +To load a TensorRT model, it first must be compiled with the `morpheus tools onnx-to-trt` utility. This utility requires additional packages to be installed. From the root of the Morpheus repo, install them with: +```bash +conda env update --solver=libmamba -n morpheus --file conda/environments/model-utils_cuda-125_arch-x86_64.yaml +``` +Then build the TensorRT model with (refer `triton-model-repo/sid-minibert-trt/1/README.md` for more info): ```bash cd models/triton-model-repo/sid-minibert-trt/1 -morpheus tools onnx-to-trt --input_model ../../sid-minibert-onnx/1/sid-minibert.onnx --output_model ./sid-minibert-trt_b1-8_b1-16_b1-32.engine --batches 1 8 --batches 1 16 --batches 1 32 --seq_length 256 --max_workspace_size 16000 +morpheus --log_level=info tools onnx-to-trt --input_model ../../sid-minibert-onnx/1/model.onnx --output_model ./model.plan --batches 1 8 --batches 1 16 --batches 1 32 --seq_length 256 --max_workspace_size 16000 ``` Then launch Triton: diff --git a/models/triton-model-repo/log-parsing-onnx/config.pbtxt b/models/triton-model-repo/log-parsing-onnx/config.pbtxt index 245ae9792b..21f0233ace 100644 --- a/models/triton-model-repo/log-parsing-onnx/config.pbtxt +++ b/models/triton-model-repo/log-parsing-onnx/config.pbtxt @@ -18,7 +18,7 @@ output [ { name: "output" data_type: TYPE_FP32 - dims: [ -1, 23 ] + dims: [ 256, 23 ] } ] diff --git a/models/triton-model-repo/phishing-bert-trt/1/README.md b/models/triton-model-repo/phishing-bert-trt/1/README.md deleted file mode 100644 index 7c3644943f..0000000000 --- a/models/triton-model-repo/phishing-bert-trt/1/README.md +++ /dev/null @@ -1,28 +0,0 @@ - - -# Generating TRT Models from ONNX - -This model in the `triton-model-repo` directory is intentionally missing the model file. This is due to the fact that TensorRT maximizes performance of models for a *particular machine*. Any pre-compiled TensorRT engine file at best would have poor performance and most likely would not even load on other machines. - -Therefore, it is best to compile a TensorRT engine file for on each machine that it will be run on. To facilitate this, Morpheus contains a utility to input an ONNX file and export the TensorRT engine file. To generate the necessary TensorRT engine file for this model, run the following from the same directory as this README: - -```bash -morpheus tools onnx-to-trt --input_model ../../phishing-bert-onnx/1/model.onnx --output_model ./model.plan --batches 1 8 --batches 1 16 --batches 1 32 --seq_length 128 --max_workspace_size 16000 -``` - -Note: If you get an out-of-memory error, reduce the `--max_workspace_size` argument until it will successfully run. diff --git a/models/triton-model-repo/phishing-bert-trt/config.pbtxt b/models/triton-model-repo/phishing-bert-trt/config.pbtxt deleted file mode 100644 index b5400ecbe0..0000000000 --- a/models/triton-model-repo/phishing-bert-trt/config.pbtxt +++ /dev/null @@ -1,36 +0,0 @@ -name: "phishing-bert-trt" -platform: "tensorrt_plan" -max_batch_size: 32 - -input [ - { - name: "input_ids" - data_type: TYPE_INT32 - dims: [ 128 ] - }, - { - name: "attention_mask" - data_type: TYPE_INT32 - dims: [ 128 ] - } -] -output [ - { - name: "output" - data_type: TYPE_FP32 - dims: [ 2 ] - } -] - -dynamic_batching { - preferred_batch_size: [ 1, 4, 8, 12, 16, 20, 24, 28, 32 ] - max_queue_delay_microseconds: 50000 -} - -instance_group [ - { - count: 1 - kind: KIND_GPU - profile: ["2"] - } -] diff --git a/models/triton-model-repo/sid-minibert-trt/1/README.md b/models/triton-model-repo/sid-minibert-trt/1/README.md deleted file mode 100644 index 65a39c7ba5..0000000000 --- a/models/triton-model-repo/sid-minibert-trt/1/README.md +++ /dev/null @@ -1,28 +0,0 @@ - - -# Generating TRT Models from ONNX - -This model in the `triton-model-repo` directory is intentionally missing the model file. This is due to the fact that TensorRT maximizes performance of models for a *particular machine*. Any pre-compiled TensorRT engine file at best would have poor performance and most likely would not even load on other machines. - -Therefore, it is best to compile a TensorRT engine file for on each machine that it will be run on. To facilitate this, Morpheus contains a utility to input an ONNX file and export the TensorRT engine file. To generate the necessary TensorRT engine file for this model, run the following from the same directory as this README: - -```bash -morpheus tools onnx-to-trt --input_model ../../sid-minibert-onnx/1/model.onnx --output_model ./model.plan --batches 1 8 --batches 1 16 --batches 1 32 --seq_length 256 --max_workspace_size 16000 -``` - -Note: If you get an out-of-memory error, reduce the `--max_workspace_size` argument until it will successfully run. diff --git a/models/triton-model-repo/sid-minibert-trt/config.pbtxt b/models/triton-model-repo/sid-minibert-trt/config.pbtxt deleted file mode 100644 index a2dba952d2..0000000000 --- a/models/triton-model-repo/sid-minibert-trt/config.pbtxt +++ /dev/null @@ -1,37 +0,0 @@ -name: "sid-minibert-trt" -platform: "tensorrt_plan" -max_batch_size: 32 -# default_model_filename: "sid-minibert-trt_b1-8_b1-16_b1-32.engine" - -input [ - { - name: "input_ids" - data_type: TYPE_INT32 - dims: [ 256 ] - }, - { - name: "attention_mask" - data_type: TYPE_INT32 - dims: [ 256 ] - } -] -output [ - { - name: "output" - data_type: TYPE_FP32 - dims: [ 10 ] - } -] - -dynamic_batching { - preferred_batch_size: [ 1, 4, 8, 12, 16, 20, 24, 28, 32 ] - max_queue_delay_microseconds: 50000 -} - -instance_group [ - { - count: 1 - kind: KIND_GPU - profile: ["2"] - } -] diff --git a/python/morpheus/morpheus/_lib/common/__init__.pyi b/python/morpheus/morpheus/_lib/common/__init__.pyi index 7e11e81ccd..38f3c5fd66 100644 --- a/python/morpheus/morpheus/_lib/common/__init__.pyi +++ b/python/morpheus/morpheus/_lib/common/__init__.pyi @@ -110,7 +110,7 @@ class FilterSource(): __members__: dict # value = {'Auto': , 'TENSOR': , 'DATAFRAME': } pass class HttpEndpoint(): - def __init__(self, py_parse_fn: function, url: str, method: str) -> None: ... + def __init__(self, py_parse_fn: function, url: str, method: str, include_headers: bool = False) -> None: ... pass class HttpServer(): def __enter__(self) -> HttpServer: ... diff --git a/python/morpheus/morpheus/_lib/common/module.cpp b/python/morpheus/morpheus/_lib/common/module.cpp index f9ba779f10..8692f0a5c4 100644 --- a/python/morpheus/morpheus/_lib/common/module.cpp +++ b/python/morpheus/morpheus/_lib/common/module.cpp @@ -149,7 +149,11 @@ PYBIND11_MODULE(common, _module) .value("DATAFRAME", FilterSource::DATAFRAME); py::class_>(_module, "HttpEndpoint") - .def(py::init<>(&HttpEndpointInterfaceProxy::init), py::arg("py_parse_fn"), py::arg("url"), py::arg("method")); + .def(py::init<>(&HttpEndpointInterfaceProxy::init), + py::arg("py_parse_fn"), + py::arg("url"), + py::arg("method"), + py::arg("include_headers") = false); py::class_>(_module, "HttpServer") .def(py::init<>(&HttpServerInterfaceProxy::init), diff --git a/python/morpheus/morpheus/_lib/cudf_helpers.pyx b/python/morpheus/morpheus/_lib/cudf_helpers.pyx index 2345978b92..6c23a1b543 100644 --- a/python/morpheus/morpheus/_lib/cudf_helpers.pyx +++ b/python/morpheus/morpheus/_lib/cudf_helpers.pyx @@ -29,6 +29,228 @@ from cudf._lib.column cimport Column from cudf._lib.utils cimport data_from_unique_ptr from cudf._lib.utils cimport table_view_from_table +##### THE FOLLOWING CODE IS COPIED FROM CUDF AND SHOULD BE REMOVED WHEN UPDATING TO cudf>=24.12 ##### +# see https://github.com/rapidsai/cudf/pull/17193 for details + +# isort: off + +# imports needed for get_element, which is required by from_column_view_with_fix +cimport pylibcudf.libcudf.copying as cpp_copying +from pylibcudf.libcudf.column.column_view cimport column_view +from libcpp.memory cimport make_unique, unique_ptr +from pylibcudf.libcudf.scalar.scalar cimport scalar +from cudf._lib.scalar cimport DeviceScalar + +# imports needed for from_column_view_with_fix +import rmm +from libc.stdint cimport uintptr_t +from cudf.core.buffer import ( + # Buffer, + ExposureTrackedBuffer, + SpillableBuffer, + # acquire_spill_lock, + as_buffer, + # cuda_array_interface_wrapper, +) +cimport pylibcudf.libcudf.types as libcudf_types +from cudf._lib.types cimport ( + dtype_from_column_view, + # dtype_to_data_type, + # dtype_to_pylibcudf_type, +) +from cudf._lib.null_mask import bitmask_allocation_size_bytes + +# isort: on + +cdef get_element(column_view col_view, size_type index): + + cdef unique_ptr[scalar] c_output + with nogil: + c_output = move( + cpp_copying.get_element(col_view, index) + ) + + return DeviceScalar.from_unique_ptr( + move(c_output), dtype=dtype_from_column_view(col_view) + ) + +cdef Column from_column_view_with_fix(column_view cv, object owner): + """ + Given a ``cudf::column_view``, constructs a ``cudf.Column`` from it, + along with referencing an ``owner`` Python object that owns the memory + lifetime. If ``owner`` is a ``cudf.Column``, we reach inside of it and + make the owner of each newly created ``Buffer`` the respective + ``Buffer`` from the ``owner`` ``cudf.Column``. + If ``owner`` is ``None``, we allocate new memory for the resulting + ``cudf.Column``. + """ + column_owner = isinstance(owner, Column) + mask_owner = owner + if column_owner and isinstance(owner.dtype, cudf.CategoricalDtype): + owner = owner.base_children[0] + + size = cv.size() + offset = cv.offset() + dtype = dtype_from_column_view(cv) + dtype_itemsize = getattr(dtype, "itemsize", 1) + + data_ptr = (cv.head[void]()) + data = None + base_size = size + offset + data_owner = owner + + if column_owner: + data_owner = owner.base_data + mask_owner = mask_owner.base_mask + base_size = owner.base_size + base_nbytes = base_size * dtype_itemsize + # special case for string column + is_string_column = (cv.type().id() == libcudf_types.type_id.STRING) + if is_string_column: + if cv.num_children() == 0: + base_nbytes = 0 + else: + # get the size from offset child column (device to host copy) + offsets_column_index = 0 + offset_child_column = cv.child(offsets_column_index) + if offset_child_column.size() == 0: + base_nbytes = 0 + else: + chars_size = get_element( + offset_child_column, offset_child_column.size()-1).value + base_nbytes = chars_size + + if data_ptr: + if data_owner is None: + buffer_size = ( + base_nbytes + if is_string_column + else ((size + offset) * dtype_itemsize) + ) + data = as_buffer( + rmm.DeviceBuffer(ptr=data_ptr, + size=buffer_size) + ) + elif ( + column_owner and + isinstance(data_owner, ExposureTrackedBuffer) + ): + data = as_buffer( + data=data_ptr, + size=base_nbytes, + owner=data_owner, + exposed=False, + ) + elif ( + # This is an optimization of the most common case where + # from_column_view creates a "view" that is identical to + # the owner. + column_owner and + isinstance(data_owner, SpillableBuffer) and + # We check that `data_owner` is spill locked (not spillable) + # and that it points to the same memory as `data_ptr`. + not data_owner.spillable and + data_owner.memory_info() == (data_ptr, base_nbytes, "gpu") + ): + data = data_owner + else: + # At this point we don't know the relationship between data_ptr + # and data_owner thus we mark both of them exposed. + # TODO: try to discover their relationship and create a + # SpillableBufferSlice instead. + data = as_buffer( + data=data_ptr, + size=base_nbytes, + owner=data_owner, + exposed=True, + ) + if isinstance(data_owner, ExposureTrackedBuffer): + # accessing the pointer marks it exposed permanently. + data_owner.mark_exposed() + elif isinstance(data_owner, SpillableBuffer): + if data_owner.is_spilled: + raise ValueError( + f"{data_owner} is spilled, which invalidates " + f"the exposed data_ptr ({hex(data_ptr)})" + ) + # accessing the pointer marks it exposed permanently. + data_owner.mark_exposed() + else: + data = as_buffer( + rmm.DeviceBuffer(ptr=data_ptr, size=0) + ) + + mask = None + mask_ptr = (cv.null_mask()) + if mask_ptr: + if mask_owner is None: + if column_owner: + # if we reached here, it means `owner` is a `Column` + # that does not have a null mask, but `cv` thinks it + # should have a null mask. This can happen in the + # following sequence of events: + # + # 1) `cv` is constructed as a view into a + # `cudf::column` that is nullable (i.e., it has + # a null mask), but contains no nulls. + # 2) `owner`, a `Column`, is constructed from the + # same `cudf::column`. Because `cudf::column` + # is memory owning, `owner` takes ownership of + # the memory owned by the + # `cudf::column`. Because the column has a null + # count of 0, it may choose to discard the null + # mask. + # 3) Now, `cv` points to a discarded null mask. + # + # TL;DR: we should not include a null mask in the + # result: + mask = None + else: + mask = as_buffer( + rmm.DeviceBuffer( + ptr=mask_ptr, + size=bitmask_allocation_size_bytes(base_size) + ) + ) + else: + mask = as_buffer( + data=mask_ptr, + size=bitmask_allocation_size_bytes(base_size), + owner=mask_owner, + exposed=True + ) + + if cv.has_nulls(): + null_count = cv.null_count() + else: + null_count = 0 + + children = [] + for child_index in range(cv.num_children()): + child_owner = owner + if column_owner: + child_owner = owner.base_children[child_index] + children.append( + from_column_view_with_fix( + cv.child(child_index), + child_owner + ) + ) + children = tuple(children) + + result = cudf.core.column.build_column( + data=data, + dtype=dtype, + mask=mask, + size=size, + offset=offset, + null_count=null_count, + children=tuple(children) + ) + + return result + +##### THE PREVIOUS CODE IS COPIED FROM CUDF AND SHOULD BE REMOVED WHEN UPDATING TO cudf>=24.12 ##### cdef vector[string] get_column_names(object tbl, object index): cdef vector[string] column_names @@ -188,7 +410,7 @@ cdef public api: if table_owner: column_owner = owner._index._columns[column_idx] index_columns.append( - Column.from_column_view( + from_column_view_with_fix( tv.column(column_idx), column_owner ) @@ -205,7 +427,7 @@ cdef public api: if table_owner: column_owner = owner._columns[column_indices[source_column_idx]] data_columns.append( - Column.from_column_view(tv.column(column_idx), column_owner) + from_column_view_with_fix(tv.column(column_idx), column_owner) ) column_idx += 1 source_column_idx += 1 diff --git a/python/morpheus/morpheus/_lib/cudf_helpers/__init__.pyi b/python/morpheus/morpheus/_lib/cudf_helpers/__init__.pyi index 4ce3dd3269..f4acdbedb6 100644 --- a/python/morpheus/morpheus/_lib/cudf_helpers/__init__.pyi +++ b/python/morpheus/morpheus/_lib/cudf_helpers/__init__.pyi @@ -1,14 +1,24 @@ from __future__ import annotations import morpheus._lib.cudf_helpers import typing +from cudf.core.buffer.exposure_tracked_buffer import ExposureTrackedBuffer +from cudf.core.buffer.spillable_buffer import SpillableBuffer from cudf.core.dtypes import StructDtype +import _cython_3_0_11 import cudf +import rmm __all__ = [ + "ExposureTrackedBuffer", + "SpillableBuffer", "StructDtype", - "cudf" + "as_buffer", + "bitmask_allocation_size_bytes", + "cudf", + "rmm" ] __pyx_capi__: dict # value = {'make_table_from_table_with_metadata': , 'make_table_from_table_info_data': , 'make_table_info_data_from_table': , 'data_from_table_view_indexed': } __test__ = {} +bitmask_allocation_size_bytes: _cython_3_0_11.cython_function_or_method # value = diff --git a/python/morpheus/morpheus/_lib/include/morpheus/messages/control.hpp b/python/morpheus/morpheus/_lib/include/morpheus/messages/control.hpp index c10bdc4a78..6f14d93037 100644 --- a/python/morpheus/morpheus/_lib/include/morpheus/messages/control.hpp +++ b/python/morpheus/morpheus/_lib/include/morpheus/messages/control.hpp @@ -235,6 +235,13 @@ class MORPHEUS_EXPORT ControlMessage */ std::optional get_timestamp(const std::string& key, bool fail_if_nonexist = false); + /** + * @brief Return a reference to the timestamps map + * + * @return A const map reference containing timestamps + */ + const std::map& get_timestamps() const; + /** * @brief Retrieves timestamps for all keys that match a regex pattern. * @@ -340,6 +347,13 @@ struct MORPHEUS_EXPORT ControlMessageProxy */ static pybind11::object get_timestamp(ControlMessage& self, const std::string& key, bool fail_if_nonexist = false); + /** + * @brief Return all timestamps + * + * @return A Python dictionary of timestamps + */ + static pybind11::dict get_timestamps(ControlMessage& self); + /** * @brief Retrieves timestamps for all keys that match a regex pattern from the ControlMessage object. * diff --git a/python/morpheus/morpheus/_lib/include/morpheus/objects/memory_descriptor.hpp b/python/morpheus/morpheus/_lib/include/morpheus/objects/memory_descriptor.hpp index 6c55baa85c..f9384a910f 100644 --- a/python/morpheus/morpheus/_lib/include/morpheus/objects/memory_descriptor.hpp +++ b/python/morpheus/morpheus/_lib/include/morpheus/objects/memory_descriptor.hpp @@ -19,10 +19,9 @@ #include "morpheus/export.h" +#include #include -#include "cuda/memory_resource" - namespace morpheus { /** * @addtogroup objects diff --git a/python/morpheus/morpheus/_lib/include/morpheus/objects/python_data_table.hpp b/python/morpheus/morpheus/_lib/include/morpheus/objects/python_data_table.hpp index 9944fa5302..4a0a222edb 100644 --- a/python/morpheus/morpheus/_lib/include/morpheus/objects/python_data_table.hpp +++ b/python/morpheus/morpheus/_lib/include/morpheus/objects/python_data_table.hpp @@ -44,7 +44,7 @@ struct PyDataTable : public IDataTable * @param py_table */ PyDataTable(pybind11::object&& py_table); - ~PyDataTable(); + ~PyDataTable() override; /** * @brief cuDF table rows count diff --git a/python/morpheus/morpheus/_lib/include/morpheus/objects/table_info.hpp b/python/morpheus/morpheus/_lib/include/morpheus/objects/table_info.hpp index d4e719abd2..710158aec3 100644 --- a/python/morpheus/morpheus/_lib/include/morpheus/objects/table_info.hpp +++ b/python/morpheus/morpheus/_lib/include/morpheus/objects/table_info.hpp @@ -70,7 +70,7 @@ struct MORPHEUS_EXPORT TableInfoBase std::vector get_column_names() const; /** - * @brief Get size of a index names in a data table + * @brief Get the number of indices in a data table * * @return cudf::size_type */ diff --git a/python/morpheus/morpheus/_lib/include/morpheus/objects/tensor_object.hpp b/python/morpheus/morpheus/_lib/include/morpheus/objects/tensor_object.hpp index df3fcf8912..1328c021e5 100644 --- a/python/morpheus/morpheus/_lib/include/morpheus/objects/tensor_object.hpp +++ b/python/morpheus/morpheus/_lib/include/morpheus/objects/tensor_object.hpp @@ -294,7 +294,7 @@ struct TensorObject final } template - T read_element(const TensorIndex (&idx)[N]) const + T read_element(const TensorIndex (&idx)[N]) const // NOLINT(modernize-avoid-c-arrays) { auto stride = this->get_stride(); auto shape = this->get_shape(); diff --git a/python/morpheus/morpheus/_lib/include/morpheus/stages/deserialize.hpp b/python/morpheus/morpheus/_lib/include/morpheus/stages/deserialize.hpp index bb20954580..6f4381b6f9 100644 --- a/python/morpheus/morpheus/_lib/include/morpheus/stages/deserialize.hpp +++ b/python/morpheus/morpheus/_lib/include/morpheus/stages/deserialize.hpp @@ -17,10 +17,11 @@ #pragma once -#include "morpheus/export.h" // for MORPHEUS_EXPORT -#include "morpheus/messages/control.hpp" // for ControlMessage -#include "morpheus/messages/meta.hpp" // for MessageMeta -#include "morpheus/types.hpp" // for TensorIndex +#include "morpheus/export.h" // for MORPHEUS_EXPORT +#include "morpheus/messages/control.hpp" // for ControlMessage +#include "morpheus/messages/meta.hpp" // for MessageMeta +#include "morpheus/types.hpp" // for TensorIndex +#include "morpheus/utilities/json_types.hpp" // for control_message_task_t #include // for operator<< #include // for Builder @@ -44,14 +45,6 @@ namespace morpheus { * @file */ -using cm_task_t = std::pair; - -void make_output_message(std::shared_ptr& incoming_message, - TensorIndex start, - TensorIndex stop, - cm_task_t* task, - std::shared_ptr& windowed_message); - /****** DeserializationStage********************************/ class MORPHEUS_EXPORT DeserializeStage : public mrc::pymrc::PythonNode, std::shared_ptr> @@ -71,8 +64,8 @@ class MORPHEUS_EXPORT DeserializeStage * @param task Optional task to be added to all outgoing `ControlMessage`s */ DeserializeStage(TensorIndex batch_size, - bool ensure_sliceable_index = true, - std::unique_ptr task = nullptr) : + bool ensure_sliceable_index = true, + std::unique_ptr task = nullptr) : base_t(base_t::op_factory_from_sub_fn(build_operator())), m_batch_size(batch_size), m_ensure_sliceable_index(ensure_sliceable_index), @@ -83,7 +76,7 @@ class MORPHEUS_EXPORT DeserializeStage TensorIndex m_batch_size; bool m_ensure_sliceable_index{true}; - std::unique_ptr m_task{nullptr}; + std::unique_ptr m_task{nullptr}; }; /****** DeserializationStageInterfaceProxy******************/ diff --git a/python/morpheus/morpheus/_lib/include/morpheus/stages/http_server_source_stage.hpp b/python/morpheus/morpheus/_lib/include/morpheus/stages/http_server_source_stage.hpp index 95167f8b9b..ea2ab50eed 100644 --- a/python/morpheus/morpheus/_lib/include/morpheus/stages/http_server_source_stage.hpp +++ b/python/morpheus/morpheus/_lib/include/morpheus/stages/http_server_source_stage.hpp @@ -18,31 +18,62 @@ #pragma once #include "morpheus/export.h" // for exporting symbols +#include "morpheus/messages/control.hpp" // for ControlMessage #include "morpheus/messages/meta.hpp" // for MessageMeta #include "morpheus/utilities/http_server.hpp" // for HttpServer +#include "morpheus/utilities/json_types.hpp" // for control_message_task_t & json_t -#include // for buffered_channel -#include // for context -#include // for table_with_metadata -#include // for segment::Builder -#include // for segment::Object -#include // for PythonSource -#include // for subscriber - -#include // for atomic -#include // for duration -#include // for size_t -#include // for int64_t -#include // for shared_ptr & unique_ptr -#include // for string & to_string +#include // for int_to_status, status +#include // for buffered_channel +#include // for channel_op_status +#include // for sleep_for +#include // for json_reader_options & read_json +#include // for table_with_metadata +#include // for CHECK & LOG +#include // for segment::Builder +#include // for segment::Object +#include // for json +#include // for pybind11::object +#include // for PythonSource +#include // for subscriber + +#include // for atomic +#include // for duration +#include // for size_t +#include // for int64_t +#include // for std::exception +#include // for shared_ptr & unique_ptr +#include // needed by GLOG +#include // for std::runtime_error +#include // for string & to_string +#include // for make_tuple +#include // for std::move & pair +#include // for vector // IWYU thinks we're using thread::operator<< // IWYU pragma: no_include +// IWYU thinks we need the http.hpp header for int_to_status, but it's defined in status.hpp +// IWYU pragma: no_include namespace morpheus { -using table_t = std::unique_ptr; +using table_with_http_fields_t = std::pair; +using table_t = std::unique_ptr; + using request_queue_t = boost::fibers::buffered_channel; -/****** Component public implementations *******************/ +class SourceStageStopAfter : public std::exception +{}; + +// Per type overloads for producing the output message +void make_output_message(std::shared_ptr& incoming_message, + control_message_task_t* task, + morpheus::utilities::json_t&& http_fields, + std::shared_ptr& out_message); + +void make_output_message(std::shared_ptr& incoming_message, + control_message_task_t* task, + morpheus::utilities::json_t&& http_fields, + std::shared_ptr& out_message); + /****** HttpServerSourceStage *************************************/ /** @@ -50,34 +81,60 @@ using request_queue_t = boost::fibers::buffered_channel; * @{ * @file */ - -// TODO(dagardner): optionally add headers to the dataframe - -class MORPHEUS_EXPORT HttpServerSourceStage : public mrc::pymrc::PythonSource> +template +class MORPHEUS_EXPORT HttpServerSourceStage : public mrc::pymrc::PythonSource> { public: - using base_t = mrc::pymrc::PythonSource>; + using base_t = mrc::pymrc::PythonSource>; using typename base_t::source_type_t; using typename base_t::subscriber_fn_t; - HttpServerSourceStage(std::string bind_address = "127.0.0.1", - unsigned short port = 8080, - std::string endpoint = "/message", - std::string live_endpoint = "/live", - std::string ready_endpoint = "/ready", - std::string method = "POST", - std::string live_method = "GET", - std::string ready_method = "GET", - unsigned accept_status = 201, - float sleep_time = 0.1f, - long queue_timeout = 5, - std::size_t max_queue_size = 1024, - unsigned short num_server_threads = 1, - std::size_t max_payload_size = DefaultMaxPayloadSize, - std::chrono::seconds request_timeout = std::chrono::seconds(30), - bool lines = false, - std::size_t stop_after = 0); - ~HttpServerSourceStage() override; + /** + * @brief Constructor for the HttpServerSourceStage + * + * @param bind_address The IP address to bind the server to + * @param port The TCP port to bind the server to + * @param endpoint The endpoint to listen for messages on + * @param live_endpoint The endpoint to check if the server is running + * @param ready_endpoint The endpoint to check if the server is ready to accept messages + * @param method The HTTP method to accept requests on the `endpoint` + * @param live_method The HTTP method to accept requests on the `live_endpoint` + * @param ready_method The HTTP method accept requests on the `ready_endpoint` + * @param accept_status The HTTP status code to return when a message is accepted + * @param sleep_time The time to sleep when the queue is empty + * @param queue_timeout The time to wait for the queue to accept a message + * @param max_queue_size The maximum number of messages to queue prior to blocking incoming requests + * @param num_server_threads The number of threads to run the server on + * @param max_payload_size The maximum size of the payload + * @param request_timeout The time to wait for a request to complete + * @param lines If `false`, the HTTP server will expect each request to be a JSON array of objects. If `true`, the + * HTTP server will expect each request to be a JSON object per line. + * @param stop_after The number of records to emit before stopping. Useful for testing, disabled if `0`. + * @param task When `OutputT=ControlMessage`, optional task to be added to all outgoing messagess, triggers an + * assertion error for all other types. + */ + HttpServerSourceStage(std::string bind_address = "127.0.0.1", + unsigned short port = 8080, + std::string endpoint = "/message", + std::string live_endpoint = "/live", + std::string ready_endpoint = "/ready", + std::string method = "POST", + std::string live_method = "GET", + std::string ready_method = "GET", + unsigned accept_status = 201, + float sleep_time = 0.1f, + long queue_timeout = 5, + std::size_t max_queue_size = 1024, + unsigned short num_server_threads = 1, + std::size_t max_payload_size = DefaultMaxPayloadSize, + std::chrono::seconds request_timeout = std::chrono::seconds(30), + bool lines = false, + std::size_t stop_after = 0, + std::unique_ptr task = nullptr); + ~HttpServerSourceStage() override + { + close(); + } void close(); @@ -92,7 +149,8 @@ class MORPHEUS_EXPORT HttpServerSourceStage : public mrc::pymrc::PythonSource m_task{nullptr}; }; /****** HttpServerSourceStageInterfaceProxy***********************/ @@ -101,25 +159,330 @@ class MORPHEUS_EXPORT HttpServerSourceStage : public mrc::pymrc::PythonSource> init(mrc::segment::Builder& builder, - const std::string& name, - std::string bind_address, - unsigned short port, - std::string endpoint, - std::string live_endpoint, - std::string ready_endpoint, - std::string method, - std::string live_method, - std::string ready_method, - unsigned accept_status, - float sleep_time, - long queue_timeout, - std::size_t max_queue_size, - unsigned short num_server_threads, - std::size_t max_payload_size, - int64_t request_timeout, - bool lines, - std::size_t stop_after); + /** + * @brief Create and initialize a HttpServerSourceStage that emits MessageMeta's, and return the result + * + * @param builder : Pipeline context object reference + * @param name : Name of a stage reference + * @param bind_address The IP address to bind the server to + * @param port The TCP port to bind the server to + * @param endpoint The endpoint to listen for messages on + * @param live_endpoint The endpoint to check if the server is running + * @param ready_endpoint The endpoint to check if the server is ready to accept messages + * @param method The HTTP method to accept requests on the `endpoint` + * @param live_method The HTTP method to accept requests on the `live_endpoint` + * @param ready_method The HTTP method accept requests on the `ready_endpoint` + * @param accept_status The HTTP status code to return when a message is accepted + * @param sleep_time The time to sleep when the queue is empty + * @param queue_timeout The time to wait for the queue to accept a message + * @param max_queue_size The maximum number of messages to queue prior to blocking incoming requests + * @param num_server_threads The number of threads to run the server on + * @param max_payload_size The maximum size of the payload + * @param request_timeout The time to wait for a request to complete + * @param lines If `False`, the HTTP server will expect each request to be a JSON array of objects. If `True`, the + * HTTP server will expect each request to be a JSON object per line. + * @param stop_after The number of records to emit before stopping. Useful for testing, disabled if `0`. + */ + static std::shared_ptr>> init_meta( + mrc::segment::Builder& builder, + const std::string& name, + std::string bind_address, + unsigned short port, + std::string endpoint, + std::string live_endpoint, + std::string ready_endpoint, + std::string method, + std::string live_method, + std::string ready_method, + unsigned accept_status, + float sleep_time, + long queue_timeout, + std::size_t max_queue_size, + unsigned short num_server_threads, + std::size_t max_payload_size, + int64_t request_timeout, + bool lines, + std::size_t stop_after); + + /** + * @brief Create and initialize a HttpServerSourceStage that emits ControlMessage's, and return the result + * + * @param builder : Pipeline context object reference + * @param name : Name of a stage reference + * @param bind_address The IP address to bind the server to + * @param port The TCP port to bind the server to + * @param endpoint The endpoint to listen for messages on + * @param live_endpoint The endpoint to check if the server is running + * @param ready_endpoint The endpoint to check if the server is ready to accept messages + * @param method The HTTP method to accept requests on the `endpoint` + * @param live_method The HTTP method to accept requests on the `live_endpoint` + * @param ready_method The HTTP method accept requests on the `ready_endpoint` + * @param accept_status The HTTP status code to return when a message is accepted + * @param sleep_time The time to sleep when the queue is empty + * @param queue_timeout The time to wait for the queue to accept a message + * @param max_queue_size The maximum number of messages to queue prior to blocking incoming requests + * @param num_server_threads The number of threads to run the server on + * @param max_payload_size The maximum size of the payload + * @param request_timeout The time to wait for a request to complete + * @param lines If `False`, the HTTP server will expect each request to be a JSON array of objects. If `True`, the + * HTTP server will expect each request to be a JSON object per line. + * @param stop_after The number of records to emit before stopping. Useful for testing, disabled if `0`. + * @param task_type Optional task type to be added to all outgoing messages. When not `None`, then `task_payload` + * must also be not `None`, and vice versa. + * @param task_payload Optional json object describing the task to be added to all outgoing messages. When not + * `None`, then `task_type` must also be not `None`, and vice versa. + */ + static std::shared_ptr>> init_cm( + mrc::segment::Builder& builder, + const std::string& name, + std::string bind_address, + unsigned short port, + std::string endpoint, + std::string live_endpoint, + std::string ready_endpoint, + std::string method, + std::string live_method, + std::string ready_method, + unsigned accept_status, + float sleep_time, + long queue_timeout, + std::size_t max_queue_size, + unsigned short num_server_threads, + std::size_t max_payload_size, + int64_t request_timeout, + bool lines, + std::size_t stop_after, + const pybind11::object& task_type, + const pybind11::object& task_payload); }; + +template +HttpServerSourceStage::HttpServerSourceStage(std::string bind_address, + unsigned short port, + std::string endpoint, + std::string live_endpoint, + std::string ready_endpoint, + std::string method, + std::string live_method, + std::string ready_method, + unsigned accept_status, + float sleep_time, + long queue_timeout, + std::size_t max_queue_size, + unsigned short num_server_threads, + std::size_t max_payload_size, + std::chrono::seconds request_timeout, + bool lines, + std::size_t stop_after, + std::unique_ptr task) : + base_t(build()), + m_max_queue_size{max_queue_size}, + m_sleep_time{std::chrono::milliseconds(static_cast(sleep_time))}, + m_queue_timeout{queue_timeout}, + m_queue{max_queue_size}, + m_stop_after{stop_after}, + m_task{std::move(task)} +{ + CHECK(boost::beast::http::int_to_status(accept_status) != boost::beast::http::status::unknown) + << "Invalid HTTP status code: " << accept_status; + + request_handler_fn_t parser = [this, accept_status, lines](const tcp_endpoint_t& tcp_endpoint, + const request_t& request) { + // This function is called from one of the HTTPServer's worker threads, avoid performing any additional work + // here beyond what is strictly nessary to return a valid response to the client. We parse the payload here, + // that way we can return an appropriate error message if the payload is invalid however we stop avoid + // constructing a MessageMeta object since that would require grabbing the Python GIL, instead we push the + // libcudf table to the queue and let the subscriber handle the conversion to MessageMeta. + table_t table{nullptr}; + + try + { + std::string body{request.body()}; + cudf::io::source_info source{body.c_str(), body.size()}; + auto options = cudf::io::json_reader_options::builder(source).lines(lines); + auto cudf_table = cudf::io::read_json(options.build()); + + auto http_fields = request_headers_to_json(tcp_endpoint, request); + table = std::make_unique(std::move(cudf_table), std::move(http_fields)); + } catch (const std::exception& e) + { + // We want to log the exception locally, but we don't want to include the exception message in the response + // since that may leak sensitive information + std::string error_msg = "Error occurred converting HTTP payload to Dataframe"; + LOG(ERROR) << error_msg << ": " << e.what(); + return std::make_tuple(400u, "text/plain", error_msg, nullptr); + } + + try + { + // NOLINTNEXTLINE(clang-diagnostic-unused-value) + DCHECK_NOTNULL(table); + auto queue_status = m_queue.push_wait_for(std::move(table), m_queue_timeout); + + if (queue_status == boost::fibers::channel_op_status::success) + { + ++m_queue_cnt; + return std::make_tuple(accept_status, "text/plain", std::string(), nullptr); + } + + std::string error_msg = "HTTP payload queue is "; + switch (queue_status) + { + case boost::fibers::channel_op_status::full: + case boost::fibers::channel_op_status::timeout: { + error_msg += "full"; + break; + } + + case boost::fibers::channel_op_status::closed: { + error_msg += "closed"; + break; + } + default: { + error_msg += "in an unknown state"; + break; + } + } + + return std::make_tuple(503u, "text/plain", std::move(error_msg), nullptr); + } catch (const std::exception& e) + { + // Refer above comment about not including exception messages in the response + std::string error_msg = "Error occurred while pushing payload to queue"; + LOG(ERROR) << error_msg << ": " << e.what(); + return std::make_tuple(500u, "text/plain", error_msg, nullptr); + } + }; + + request_handler_fn_t live_parser = [this](const tcp_endpoint_t& tcp_endpoint, const request_t& request) { + if (!m_server->is_running()) + { + std::string error_msg = "Source server is not running"; + return std::make_tuple(500u, "text/plain", error_msg, nullptr); + } + + return std::make_tuple(200u, "text/plain", std::string(), nullptr); + }; + + request_handler_fn_t ready_parser = [this](const tcp_endpoint_t& tcp_endpoint, const request_t& request) { + if (!m_server->is_running()) + { + std::string error_msg = "Source server is not running"; + return std::make_tuple(500u, "text/plain", error_msg, nullptr); + } + + if (m_queue_cnt < m_max_queue_size) + { + return std::make_tuple(200u, "text/plain", std::string(), nullptr); + } + + std::string error_msg = "HTTP payload queue is full or unavailable to accept new values"; + return std::make_tuple(503u, "text/plain", std::move(error_msg), nullptr); + }; + + std::vector endpoints; + endpoints.emplace_back(parser, std::move(endpoint), std::move(method)); + endpoints.emplace_back(live_parser, std::move(live_endpoint), std::move(live_method)); + endpoints.emplace_back(ready_parser, std::move(ready_endpoint), std::move(ready_method)); + + m_server = std::make_unique( + std::move(endpoints), std::move(bind_address), port, num_server_threads, max_payload_size, request_timeout); +} + +template +HttpServerSourceStage::subscriber_fn_t HttpServerSourceStage::build() +{ + return [this](rxcpp::subscriber subscriber) -> void { + try + { + m_server->start(); + this->source_generator(subscriber); + } catch (const SourceStageStopAfter& e) + { + DLOG(INFO) << "Completed after emitting " << m_records_emitted << " records"; + } catch (const std::exception& e) + { + LOG(ERROR) << "Encountered error while listening for incoming HTTP requests: " << e.what() << std::endl; + subscriber.on_error(std::make_exception_ptr(e)); + return; + } + subscriber.on_completed(); + this->close(); + }; +} + +template +void HttpServerSourceStage::source_generator( + rxcpp::subscriber subscriber) +{ + // only check if the server is running when the queue is empty, allowing all queued messages to be processed prior + // to shutting down + bool server_running = true; + bool queue_closed = false; + while (subscriber.is_subscribed() && server_running && !queue_closed) + { + table_t table_ptr{nullptr}; + auto queue_status = m_queue.try_pop(table_ptr); + if (queue_status == boost::fibers::channel_op_status::success) + { + --m_queue_cnt; + // NOLINTNEXTLINE(clang-diagnostic-unused-value) + DCHECK_NOTNULL(table_ptr); + try + { + auto message = MessageMeta::create_from_cpp(std::move(table_ptr->first), 0); + auto num_records = message->count(); + + // When OutputT is MessageMeta, we just swap the pointers + std::shared_ptr out_message{nullptr}; + make_output_message(message, m_task.get(), std::move(table_ptr->second), out_message); + + subscriber.on_next(std::move(out_message)); + m_records_emitted += num_records; + } catch (const std::exception& e) + { + LOG(ERROR) << "Error occurred converting HTTP payload to Dataframe: " << e.what(); + } + + if (m_stop_after > 0 && m_records_emitted >= m_stop_after) + { + throw SourceStageStopAfter(); + } + } + else if (queue_status == boost::fibers::channel_op_status::empty) + { + // if the queue is empty, maybe it's because our server is not running + server_running = m_server->is_running(); + + if (server_running) + { + // Sleep when there are no messages + boost::this_fiber::sleep_for(m_sleep_time); + } + } + else if (queue_status == boost::fibers::channel_op_status::closed) + { + queue_closed = true; + } + else + { + std::string error_msg{"Unknown queue status: " + std::to_string(static_cast(queue_status))}; + LOG(ERROR) << error_msg; + throw std::runtime_error(error_msg); + } + } +} + +template +void HttpServerSourceStage::close() +{ + if (m_server) + { + m_server->stop(); // this is a no-op if the server is not running + m_server.reset(); + } + m_queue.close(); +} + /** @} */ // end of group } // namespace morpheus diff --git a/python/morpheus/morpheus/_lib/include/morpheus/utilities/http_server.hpp b/python/morpheus/morpheus/_lib/include/morpheus/utilities/http_server.hpp index cf1c631628..bfa27c69d3 100644 --- a/python/morpheus/morpheus/_lib/include/morpheus/utilities/http_server.hpp +++ b/python/morpheus/morpheus/_lib/include/morpheus/utilities/http_server.hpp @@ -17,12 +17,15 @@ #pragma once -#include "morpheus/export.h" // for exporting symbols - -#include // for io_context -#include // for tcp, tcp::acceptor, tcp::endpoint, tcp::socket -#include // for error_code -#include // for verb +#include "morpheus/export.h" // for exporting symbols +#include "morpheus/utilities/json_types.hpp" // for json_t + +#include // for io_context +#include // for tcp, tcp::acceptor, tcp::endpoint, tcp::socket +#include // for error_code +#include // for request +#include // for string_body +#include // for verb #include #include // for pybind11::function @@ -56,6 +59,26 @@ using parse_status_t = std::tuple; +// Note this is different than the http endpoint this represents the TCP connection +using tcp_endpoint_t = boost::asio::ip::tcp::endpoint; +using request_t = boost::beast::http::request; + +/** + * @brief A function that receives the TCP endpoint, and the request object. Returning an instance of `parse_status_t`. + * + * @details The function is expected to return a tuple conforming to `parse_status_t` consisting of the HTTP status + * code, mime type value for the Content-Type header, body of the response and optionally a callback function. + * If specified, the callback function which will be called once the response has been sent or failed to send, as + * indicated by a `boost::system::error_code` reference passed to the function. + * + * Refer to https://www.boost.org/doc/libs/1_74_0/libs/system/doc/html/system.html#ref_class_error_code for more + * information regarding `boost::system::error_code`. + * + * Note: This method is preferred over the payload_parse_fn_t as it provides access to the request headers. + */ +using request_handler_fn_t = + std::function; + /** * @brief A function that receives the post body and returns an HTTP status code, Content-Type string and body. * @@ -66,11 +89,24 @@ using parse_status_t = std::tuple; +using payload_parse_fn_t = std::function; constexpr std::size_t DefaultMaxPayloadSize{1024 * 1024 * 10}; // 10MB +/** + * @brief Convert the request headers to a JSON object. + * + * @param tcp_endpoint The TCP endpoint of the request. + * @param request The request object. + * @return The JSON object representing the request headers. + */ +utilities::json_t request_headers_to_json(const tcp_endpoint_t& tcp_endpoint, const request_t& request); + /** * @brief A struct that encapsulates the http endpoint attributes * @@ -78,11 +114,19 @@ constexpr std::size_t DefaultMaxPayloadSize{1024 * 1024 * 10}; // 10MB */ struct MORPHEUS_EXPORT HttpEndpoint { - HttpEndpoint(payload_parse_fn_t payload_parse_fn, std::string url, std::string method); + HttpEndpoint(request_handler_fn_t request_handler_fn, std::string&& url, const std::string& method); + HttpEndpoint(payload_parse_fn_t payload_parse_fn, std::string&& url, const std::string& method); + std::shared_ptr m_request_handler; std::shared_ptr m_parser; std::string m_url; boost::beast::http::verb m_method; + + private: + HttpEndpoint(std::shared_ptr&& request_handler_fn, + std::shared_ptr&& payload_parse_fn, + std::string&& url, + const std::string& method); }; /** @@ -171,7 +215,10 @@ class MORPHEUS_EXPORT HttpServer */ struct MORPHEUS_EXPORT HttpEndpointInterfaceProxy { - static std::shared_ptr init(pybind11::function py_parse_fn, std::string m_url, std::string m_method); + static std::shared_ptr init(pybind11::function py_parse_fn, + std::string m_url, + std::string m_method, + bool include_headers = false); }; /****** HttpServerInterfaceProxy *************************/ diff --git a/python/morpheus/morpheus/_lib/include/morpheus/utilities/json_types.hpp b/python/morpheus/morpheus/_lib/include/morpheus/utilities/json_types.hpp index 7dd4fd4516..eaf74c6837 100644 --- a/python/morpheus/morpheus/_lib/include/morpheus/utilities/json_types.hpp +++ b/python/morpheus/morpheus/_lib/include/morpheus/utilities/json_types.hpp @@ -24,12 +24,15 @@ #include // for object #include // for PyHolder -#include // for int64_t, uint64_t, uint8_t -#include // for map -#include // for allocator, string -#include // for vector - -namespace morpheus::utilities { +#include // for copy (implied usage) +#include // for int64_t, uint64_t, uint8_t +#include // for map +#include // for allocator, string +#include // for pair +#include // for vector + +namespace morpheus { +namespace utilities { /** * @brief A container class derived from std::vector to make it compatible with nlohmann::json to hold * arbitrary Python objects as bytes. @@ -113,7 +116,13 @@ class MORPHEUS_EXPORT json_dict_t : public morpheus::utilities::json_t class MORPHEUS_EXPORT json_list_t : public morpheus::utilities::json_t {}; // NOLINTEND(readability-identifier-naming) -} // namespace morpheus::utilities +} // namespace utilities + +// A task for a control message consists of a task type and a task payload, defined here as a pair since it is invalid +// to define one without the other. +using control_message_task_t = std::pair; + +} // namespace morpheus namespace nlohmann { // NOLINTBEGIN(readability-identifier-naming) diff --git a/python/morpheus/morpheus/_lib/include/morpheus/utilities/stage_util.hpp b/python/morpheus/morpheus/_lib/include/morpheus/utilities/stage_util.hpp index ce8ad6a7f3..48bf29445f 100644 --- a/python/morpheus/morpheus/_lib/include/morpheus/utilities/stage_util.hpp +++ b/python/morpheus/morpheus/_lib/include/morpheus/utilities/stage_util.hpp @@ -18,6 +18,7 @@ #pragma once #include +#include namespace morpheus { /****** Component public free function implementations************/ diff --git a/python/morpheus/morpheus/_lib/messages/__init__.pyi b/python/morpheus/morpheus/_lib/messages/__init__.pyi index 4974b93daf..2b52b3d29b 100644 --- a/python/morpheus/morpheus/_lib/messages/__init__.pyi +++ b/python/morpheus/morpheus/_lib/messages/__init__.pyi @@ -53,6 +53,7 @@ class ControlMessage(): """ Retrieve the timestamp for a given group and key. Returns None if the timestamp does not exist and fail_if_nonexist is False. """ + def get_timestamps(self) -> dict: ... def has_metadata(self, key: str) -> bool: ... def has_task(self, task_type: str) -> bool: ... def list_metadata(self) -> list: ... diff --git a/python/morpheus/morpheus/_lib/messages/module.cpp b/python/morpheus/morpheus/_lib/messages/module.cpp index af559bde58..fed31a6d11 100644 --- a/python/morpheus/morpheus/_lib/messages/module.cpp +++ b/python/morpheus/morpheus/_lib/messages/module.cpp @@ -15,8 +15,6 @@ * limitations under the License. */ -#include "pymrc/utilities/object_wrappers.hpp" - #include "morpheus/io/data_loader_registry.hpp" #include "morpheus/messages/control.hpp" #include "morpheus/messages/memory/inference_memory.hpp" @@ -43,6 +41,7 @@ #include // IWYU pragma: keep #include // IWYU pragma: keep #include +#include #include // for pymrc::import #include @@ -274,6 +273,7 @@ PYBIND11_MODULE(messages, _module) "fail_if_nonexist is False.", py::arg("key"), py::arg("fail_if_nonexist") = false) + .def("get_timestamps", &ControlMessageProxy::get_timestamps) .def("set_timestamp", &ControlMessageProxy::set_timestamp, "Set a timestamp for a given key and group.", diff --git a/python/morpheus/morpheus/_lib/src/io/deserializers.cpp b/python/morpheus/morpheus/_lib/src/io/deserializers.cpp index 032cffd57b..7f4f7b5de1 100644 --- a/python/morpheus/morpheus/_lib/src/io/deserializers.cpp +++ b/python/morpheus/morpheus/_lib/src/io/deserializers.cpp @@ -39,10 +39,10 @@ // IWYU pragma: no_include namespace { -const std::regex IndexRegex(R"(^\s*(unnamed: 0|id)\s*$)", - std::regex_constants::ECMAScript | std::regex_constants::icase); +const std::regex INDEX_REGEX(R"(^\s*(unnamed: 0|id)\s*$)", + std::regex_constants::ECMAScript | std::regex_constants::icase); -const std::regex UnnamedRegex(R"(^\s*unnamed: 0\s*$)", std::regex_constants::ECMAScript | std::regex_constants::icase); +const std::regex UNNAMED_REGEX(R"(^\s*unnamed: 0\s*$)", std::regex_constants::ECMAScript | std::regex_constants::icase); } // namespace namespace morpheus { @@ -118,7 +118,7 @@ int get_index_col_count(const cudf::io::table_with_metadata& data_table) const auto& col_name = names[0]; // Check it against some common terms - if (std::regex_search(col_name, IndexRegex)) + if (std::regex_search(col_name, INDEX_REGEX)) { index_col_count = 1; } @@ -136,7 +136,7 @@ int prepare_df_index(cudf::io::table_with_metadata& data_table) auto& col_name = data_table.metadata.schema_info[0].name; // Also, if its the hideous 'Unnamed: 0', then just use an empty string - if (std::regex_search(col_name, UnnamedRegex)) + if (std::regex_search(col_name, UNNAMED_REGEX)) { col_name.clear(); } diff --git a/python/morpheus/morpheus/_lib/src/messages/control.cpp b/python/morpheus/morpheus/_lib/src/messages/control.cpp index d20334c35a..c1a85dbcba 100644 --- a/python/morpheus/morpheus/_lib/src/messages/control.cpp +++ b/python/morpheus/morpheus/_lib/src/messages/control.cpp @@ -177,6 +177,11 @@ void ControlMessage::set_timestamp(const std::string& key, time_point_t timestam m_timestamps[key] = timestamp_ns; } +const std::map& ControlMessage::get_timestamps() const +{ + return m_timestamps; +} + std::map ControlMessage::filter_timestamp(const std::string& regex_filter) { std::map matching_timestamps; @@ -365,6 +370,11 @@ py::list ControlMessageProxy::list_metadata(ControlMessage& self) return py_keys; } +py::dict ControlMessageProxy::get_timestamps(ControlMessage& self) +{ + return py::cast(self.get_timestamps()); +} + py::dict ControlMessageProxy::filter_timestamp(ControlMessage& self, const std::string& regex_filter) { auto cpp_map = self.filter_timestamp(regex_filter); diff --git a/python/morpheus/morpheus/_lib/src/messages/meta.cpp b/python/morpheus/morpheus/_lib/src/messages/meta.cpp index 8b37633612..78b6c3f523 100644 --- a/python/morpheus/morpheus/_lib/src/messages/meta.cpp +++ b/python/morpheus/morpheus/_lib/src/messages/meta.cpp @@ -524,7 +524,14 @@ SlicedMessageMeta::SlicedMessageMeta(std::shared_ptr other, m_start(start), m_stop(stop), m_column_names(std::move(columns)) -{} +{ + auto sliced_other = std::dynamic_pointer_cast(other); + if (sliced_other) + { + m_start += sliced_other->m_start; + m_stop += sliced_other->m_start; + } +} TensorIndex SlicedMessageMeta::count() const { @@ -533,14 +540,12 @@ TensorIndex SlicedMessageMeta::count() const TableInfo SlicedMessageMeta::get_info() const { - return this->m_data->get_info().get_slice(m_start, m_stop, m_column_names); + return get_info(m_column_names); } TableInfo SlicedMessageMeta::get_info(const std::string& col_name) const { - auto full_info = this->m_data->get_info(); - - return full_info.get_slice(m_start, m_stop, {col_name}); + return get_info(std::vector{{col_name}}); } TableInfo SlicedMessageMeta::get_info(const std::vector& column_names) const diff --git a/python/morpheus/morpheus/_lib/src/modules/data_loader_module.cpp b/python/morpheus/morpheus/_lib/src/modules/data_loader_module.cpp index 2abf1edda8..351a9ea181 100644 --- a/python/morpheus/morpheus/_lib/src/modules/data_loader_module.cpp +++ b/python/morpheus/morpheus/_lib/src/modules/data_loader_module.cpp @@ -102,8 +102,8 @@ void DataLoaderModule::initialize(mrc::segment::IBuilder& builder) return m_data_loader.load(control_message); })); - register_input_port("input", loader_node); - register_output_port("output", loader_node); + builder.register_module_input("input", loader_node); + builder.register_module_output("output", loader_node); } std::string DataLoaderModule::module_type_name() const diff --git a/python/morpheus/morpheus/_lib/src/objects/dtype.cpp b/python/morpheus/morpheus/_lib/src/objects/dtype.cpp index 3f167b1e01..cbd803273f 100644 --- a/python/morpheus/morpheus/_lib/src/objects/dtype.cpp +++ b/python/morpheus/morpheus/_lib/src/objects/dtype.cpp @@ -28,7 +28,7 @@ #include namespace { -const std::map> StrToTypeId = { +const std::map> STR_TO_TYPE_ID = { {'b', {{1, morpheus::TypeId::BOOL8}}}, {'i', @@ -234,9 +234,9 @@ DType DType::from_numpy(const std::string& numpy_str) } // Now lookup in the map - auto found_type = StrToTypeId.find(type_char); + auto found_type = STR_TO_TYPE_ID.find(type_char); - if (found_type == StrToTypeId.end()) + if (found_type == STR_TO_TYPE_ID.end()) { throw std::invalid_argument(MORPHEUS_CONCAT_STR("Type char '" << type_char << "' not supported")); } diff --git a/python/morpheus/morpheus/_lib/src/objects/mutable_table_ctx_mgr.cpp b/python/morpheus/morpheus/_lib/src/objects/mutable_table_ctx_mgr.cpp index b895252767..7b5618b6d1 100644 --- a/python/morpheus/morpheus/_lib/src/objects/mutable_table_ctx_mgr.cpp +++ b/python/morpheus/morpheus/_lib/src/objects/mutable_table_ctx_mgr.cpp @@ -20,6 +20,7 @@ #include "morpheus/utilities/string_util.hpp" #include +#include // for attribute_error #include #include diff --git a/python/morpheus/morpheus/_lib/src/objects/rmm_tensor.cpp b/python/morpheus/morpheus/_lib/src/objects/rmm_tensor.cpp index 7abd593392..fc07f38c7c 100644 --- a/python/morpheus/morpheus/_lib/src/objects/rmm_tensor.cpp +++ b/python/morpheus/morpheus/_lib/src/objects/rmm_tensor.cpp @@ -29,7 +29,7 @@ #include // for cuda_stream_per_thread #include -#include // for copy, transform +#include // for copy, transform #include // for multiplies, plus, minus #include // for back_insert_iterator, back_inserter #include diff --git a/python/morpheus/morpheus/_lib/src/objects/tensor.cpp b/python/morpheus/morpheus/_lib/src/objects/tensor.cpp index 19a6741d48..94b7cbe093 100644 --- a/python/morpheus/morpheus/_lib/src/objects/tensor.cpp +++ b/python/morpheus/morpheus/_lib/src/objects/tensor.cpp @@ -27,6 +27,7 @@ #include // for MRC_CHECK_CUDA #include +#include // needed by get_element_stride #include #include #include // for move diff --git a/python/morpheus/morpheus/_lib/src/stages/deserialize.cpp b/python/morpheus/morpheus/_lib/src/stages/deserialize.cpp index 9ce6297dc1..738f3a60bb 100644 --- a/python/morpheus/morpheus/_lib/src/stages/deserialize.cpp +++ b/python/morpheus/morpheus/_lib/src/stages/deserialize.cpp @@ -18,7 +18,10 @@ #include "morpheus/stages/deserialize.hpp" #include "morpheus/messages/control.hpp" // for ControlMessage +#include "morpheus/messages/meta.hpp" // for MessageMeta, SlicedMessageMeta +#include "morpheus/objects/table_info.hpp" // for TableInfo #include "morpheus/types.hpp" // for TensorIndex +#include "morpheus/utilities/cudf_util.hpp" // for CudfHelper #include "morpheus/utilities/json_types.hpp" // for PythonByteContainer #include "morpheus/utilities/python_util.hpp" // for show_warning_message #include "morpheus/utilities/string_util.hpp" // for MORPHEUS_CONCAT_STR @@ -36,23 +39,6 @@ namespace morpheus { -void make_output_message(std::shared_ptr& incoming_message, - TensorIndex start, - TensorIndex stop, - cm_task_t* task, - std::shared_ptr& windowed_message) -{ - auto sliced_meta = std::make_shared(incoming_message, start, stop); - auto message = std::make_shared(); - message->payload(sliced_meta); - if (task) - { - message->add_task(task->first, task->second); - } - - windowed_message.swap(message); -} - DeserializeStage::subscribe_fn_t DeserializeStage::build_operator() { return [this](rxcpp::observable input, rxcpp::subscriber output) { @@ -89,9 +75,13 @@ DeserializeStage::subscribe_fn_t DeserializeStage::build_operator() { std::shared_ptr windowed_message = std::make_shared(); - auto sliced_meta = std::make_shared( + auto sliced_meta = SlicedMessageMeta( incoming_message, i, std::min(i + this->m_batch_size, incoming_message->count())); - windowed_message->payload(sliced_meta); + auto sliced_info = sliced_meta.get_info(); + + // This unforuntately requires grabbing the GIL and is a work-around for issue #2018 + auto new_meta = MessageMeta::create_from_python(CudfHelper::table_from_table_info(sliced_info)); + windowed_message->payload(new_meta); auto task = m_task.get(); if (task) @@ -119,12 +109,12 @@ std::shared_ptr> DeserializeStageInterfac const pybind11::object& task_type, const pybind11::object& task_payload) { - std::unique_ptr task{nullptr}; + std::unique_ptr task{nullptr}; if (!task_type.is_none() && !task_payload.is_none()) { - task = std::make_unique(pybind11::cast(task_type), - mrc::pymrc::cast_from_pyobject(task_payload)); + task = std::make_unique(pybind11::cast(task_type), + mrc::pymrc::cast_from_pyobject(task_payload)); } auto stage = builder.construct_object(name, batch_size, ensure_sliceable_index, std::move(task)); diff --git a/python/morpheus/morpheus/_lib/src/stages/file_source.cpp b/python/morpheus/morpheus/_lib/src/stages/file_source.cpp index c3dce33693..82532b9afe 100644 --- a/python/morpheus/morpheus/_lib/src/stages/file_source.cpp +++ b/python/morpheus/morpheus/_lib/src/stages/file_source.cpp @@ -17,9 +17,6 @@ #include "morpheus/stages/file_source.hpp" -#include "mrc/segment/object.hpp" -#include "pymrc/node.hpp" - #include "morpheus/io/deserializers.hpp" #include "morpheus/objects/file_types.hpp" #include "morpheus/objects/table_info.hpp" @@ -29,10 +26,12 @@ #include #include #include +#include #include // IWYU pragma: keep #include #include // for str_attr_accessor #include // for pybind11::int_ +#include #include #include diff --git a/python/morpheus/morpheus/_lib/src/stages/http_server_source_stage.cpp b/python/morpheus/morpheus/_lib/src/stages/http_server_source_stage.cpp index 5a09763393..7f99bcd06f 100644 --- a/python/morpheus/morpheus/_lib/src/stages/http_server_source_stage.cpp +++ b/python/morpheus/morpheus/_lib/src/stages/http_server_source_stage.cpp @@ -17,30 +17,82 @@ #include "morpheus/stages/http_server_source_stage.hpp" -#include // for int_to_status, status -#include // for channel_op_status -#include // for sleep_for -#include // for json_reader_options & read_json -#include // for CHECK & LOG - -#include // for std::exception -#include // needed by GLOG -#include // for std::runtime_error -#include // for std::this_thread::sleep_for -#include // for make_tuple -#include // for std::move -#include // for vector -// IWYU thinks we need more boost headers than we need as int_to_status is defined in status.hpp -// IWYU pragma: no_include +#include // for cast +#include // for cast_from_pyobject namespace morpheus { -class SourceStageStopAfter : public std::exception -{}; +void make_output_message(std::shared_ptr& incoming_message, + control_message_task_t* task, + morpheus::utilities::json_t&& http_fields, + std::shared_ptr& out_message) +{ + DCHECK_EQ(task, nullptr) << "Tasks are not supported for MessageMeta"; + out_message.swap(incoming_message); +} + +void make_output_message(std::shared_ptr& incoming_message, + control_message_task_t* task, + morpheus::utilities::json_t&& http_fields, + std::shared_ptr& out_message) +{ + utilities::json_t cm_config = {{"metadata", {{"http_fields", http_fields}}}}; + auto cm_msg = std::make_shared(cm_config); + cm_msg->payload(incoming_message); + if (task) + { + cm_msg->add_task(task->first, task->second); + } + out_message.swap(cm_msg); +} + +// ************ HttpServerSourceStageInterfaceProxy ************ // +std::shared_ptr>> +HttpServerSourceStageInterfaceProxy::init_meta(mrc::segment::Builder& builder, + const std::string& name, + std::string bind_address, + unsigned short port, + std::string endpoint, + std::string live_endpoint, + std::string ready_endpoint, + std::string method, + std::string live_method, + std::string ready_method, + unsigned accept_status, + float sleep_time, + long queue_timeout, + std::size_t max_queue_size, + unsigned short num_server_threads, + std::size_t max_payload_size, + int64_t request_timeout, + bool lines, + std::size_t stop_after) +{ + return builder.construct_object>(name, + std::move(bind_address), + port, + std::move(endpoint), + std::move(live_endpoint), + std::move(ready_endpoint), + std::move(method), + std::move(live_method), + std::move(ready_method), + accept_status, + sleep_time, + queue_timeout, + max_queue_size, + num_server_threads, + max_payload_size, + std::chrono::seconds(request_timeout), + lines, + stop_after, + nullptr); +} -// Component public implementations -// ************ HttpServerSourceStage ************* // -HttpServerSourceStage::HttpServerSourceStage(std::string bind_address, +std::shared_ptr>> +HttpServerSourceStageInterfaceProxy::init_cm(mrc::segment::Builder& builder, + const std::string& name, + std::string bind_address, unsigned short port, std::string endpoint, std::string live_endpoint, @@ -54,241 +106,38 @@ HttpServerSourceStage::HttpServerSourceStage(std::string bind_address, std::size_t max_queue_size, unsigned short num_server_threads, std::size_t max_payload_size, - std::chrono::seconds request_timeout, + int64_t request_timeout, bool lines, - std::size_t stop_after) : - PythonSource(build()), - m_max_queue_size{max_queue_size}, - m_sleep_time{std::chrono::milliseconds(static_cast(sleep_time))}, - m_queue_timeout{queue_timeout}, - m_queue{max_queue_size}, - m_stop_after{stop_after}, - m_records_emitted{0} -{ - CHECK(boost::beast::http::int_to_status(accept_status) != boost::beast::http::status::unknown) - << "Invalid HTTP status code: " << accept_status; - - payload_parse_fn_t parser = [this, accept_status, lines](const std::string& payload) { - std::unique_ptr table{nullptr}; - try - { - cudf::io::source_info source{payload.c_str(), payload.size()}; - auto options = cudf::io::json_reader_options::builder(source).lines(lines); - table = std::make_unique(cudf::io::read_json(options.build())); - } catch (const std::exception& e) - { - std::string error_msg = "Error occurred converting HTTP payload to Dataframe"; - LOG(ERROR) << error_msg << ": " << e.what(); - return std::make_tuple(400u, "text/plain", error_msg, nullptr); - } - - try - { - // NOLINTNEXTLINE(clang-diagnostic-unused-value) - DCHECK_NOTNULL(table); - auto queue_status = m_queue.push_wait_for(std::move(table), m_queue_timeout); - - if (queue_status == boost::fibers::channel_op_status::success) - { - m_queue_cnt++; - return std::make_tuple(accept_status, "text/plain", std::string(), nullptr); - } - - std::string error_msg = "HTTP payload queue is "; - switch (queue_status) - { - case boost::fibers::channel_op_status::full: - case boost::fibers::channel_op_status::timeout: { - error_msg += "full"; - break; - } - - case boost::fibers::channel_op_status::closed: { - error_msg += "closed"; - break; - } - default: { - error_msg += "in an unknown state"; - break; - } - } - - return std::make_tuple(503u, "text/plain", std::move(error_msg), nullptr); - } catch (const std::exception& e) - { - std::string error_msg = "Error occurred while pushing payload to queue"; - LOG(ERROR) << error_msg << ": " << e.what(); - return std::make_tuple(500u, "text/plain", error_msg, nullptr); - } - }; - - payload_parse_fn_t live_parser = [this, accept_status, lines](const std::string& payload) { - if (!m_server->is_running()) - { - std::string error_msg = "Source server is not running"; - return std::make_tuple(500u, "text/plain", error_msg, nullptr); - } - - return std::make_tuple(accept_status, "text/plain", std::string(), nullptr); - }; - - payload_parse_fn_t ready_parser = [this, accept_status, lines](const std::string& payload) { - if (!m_server->is_running()) - { - std::string error_msg = "Source server is not running"; - return std::make_tuple(500u, "text/plain", error_msg, nullptr); - } - - if (m_queue_cnt < m_max_queue_size) - { - return std::make_tuple(accept_status, "text/plain", std::string(), nullptr); - } - - std::string error_msg = "HTTP payload queue is full or unavailable to accept new values"; - return std::make_tuple(503u, "text/plain", std::move(error_msg), nullptr); - }; - - std::vector endpoints; - endpoints.emplace_back(parser, endpoint, method); - endpoints.emplace_back(live_parser, live_endpoint, live_method); - endpoints.emplace_back(ready_parser, ready_endpoint, ready_method); - - m_server = std::make_unique( - std::move(endpoints), std::move(bind_address), port, num_server_threads, max_payload_size, request_timeout); -} - -HttpServerSourceStage::subscriber_fn_t HttpServerSourceStage::build() + std::size_t stop_after, + const pybind11::object& task_type, + const pybind11::object& task_payload) { - return [this](rxcpp::subscriber subscriber) -> void { - try - { - m_server->start(); - this->source_generator(subscriber); - } catch (const SourceStageStopAfter& e) - { - DLOG(INFO) << "Completed after emitting " << m_records_emitted << " records"; - } catch (const std::exception& e) - { - LOG(ERROR) << "Encountered error while listening for incoming HTTP requests: " << e.what() << std::endl; - subscriber.on_error(std::make_exception_ptr(e)); - return; - } - subscriber.on_completed(); - this->close(); - }; -} - -void HttpServerSourceStage::source_generator(rxcpp::subscriber subscriber) -{ - // only check if the server is running when the queue is empty, allowing all queued messages to be processed prior - // to shutting down - bool server_running = true; - bool queue_closed = false; - while (subscriber.is_subscribed() && server_running && !queue_closed) - { - table_t table_ptr{nullptr}; - auto queue_status = m_queue.try_pop(table_ptr); - if (queue_status == boost::fibers::channel_op_status::success) - { - // NOLINTNEXTLINE(clang-diagnostic-unused-value) - m_queue_cnt--; - DCHECK_NOTNULL(table_ptr); - try - { - auto message = MessageMeta::create_from_cpp(std::move(*table_ptr), 0); - auto num_records = message->count(); - subscriber.on_next(std::move(message)); - m_records_emitted += num_records; - } catch (const std::exception& e) - { - LOG(ERROR) << "Error occurred converting HTTP payload to Dataframe: " << e.what(); - } - - if (m_stop_after > 0 && m_records_emitted >= m_stop_after) - { - throw SourceStageStopAfter(); - } - } - else if (queue_status == boost::fibers::channel_op_status::empty) - { - // if the queue is empty, maybe it's because our server is not running - server_running = m_server->is_running(); + std::unique_ptr task{nullptr}; - if (server_running) - { - // Sleep when there are no messages - boost::this_fiber::sleep_for(m_sleep_time); - } - } - else if (queue_status == boost::fibers::channel_op_status::closed) - { - queue_closed = true; - } - else - { - std::string error_msg{"Unknown queue status: " + std::to_string(static_cast(queue_status))}; - LOG(ERROR) << error_msg; - throw std::runtime_error(error_msg); - } - } -} - -void HttpServerSourceStage::close() -{ - if (m_server) + if (!task_type.is_none() && !task_payload.is_none()) { - m_server->stop(); // this is a no-op if the server is not running - m_server.reset(); + task = std::make_unique(pybind11::cast(task_type), + mrc::pymrc::cast_from_pyobject(task_payload)); } - m_queue.close(); -} - -HttpServerSourceStage::~HttpServerSourceStage() -{ - close(); -} - -// ************ HttpServerSourceStageInterfaceProxy ************ // -std::shared_ptr> HttpServerSourceStageInterfaceProxy::init( - mrc::segment::Builder& builder, - const std::string& name, - std::string bind_address, - unsigned short port, - std::string endpoint, - std::string live_endpoint, - std::string ready_endpoint, - std::string method, - std::string live_method, - std::string ready_method, - unsigned accept_status, - float sleep_time, - long queue_timeout, - std::size_t max_queue_size, - unsigned short num_server_threads, - std::size_t max_payload_size, - int64_t request_timeout, - bool lines, - std::size_t stop_after) -{ - return builder.construct_object( - name, - std::move(bind_address), - port, - std::move(endpoint), - std::move(live_endpoint), - std::move(ready_endpoint), - std::move(method), - std::move(live_method), - std::move(ready_method), - accept_status, - sleep_time, - queue_timeout, - max_queue_size, - num_server_threads, - max_payload_size, - std::chrono::seconds(request_timeout), - lines, - stop_after); + return builder.construct_object>(name, + std::move(bind_address), + port, + std::move(endpoint), + std::move(live_endpoint), + std::move(ready_endpoint), + std::move(method), + std::move(live_method), + std::move(ready_method), + accept_status, + sleep_time, + queue_timeout, + max_queue_size, + num_server_threads, + max_payload_size, + std::chrono::seconds(request_timeout), + lines, + stop_after, + std::move(task)); } } // namespace morpheus diff --git a/python/morpheus/morpheus/_lib/src/stages/kafka_source.cpp b/python/morpheus/morpheus/_lib/src/stages/kafka_source.cpp index 1bb6ea369d..2aa02a2598 100644 --- a/python/morpheus/morpheus/_lib/src/stages/kafka_source.cpp +++ b/python/morpheus/morpheus/_lib/src/stages/kafka_source.cpp @@ -17,9 +17,6 @@ #include "morpheus/stages/kafka_source.hpp" -#include "mrc/segment/object.hpp" -#include "pymrc/utilities/function_wrappers.hpp" // for PyFuncWrapper - #include "morpheus/messages/meta.hpp" #include "morpheus/utilities/stage_util.hpp" #include "morpheus/utilities/string_util.hpp" @@ -31,11 +28,13 @@ #include #include #include +#include #include // for SharedFuture #include #include #include #include +#include // for PyFuncWrapper #include // for find, min, transform #include diff --git a/python/morpheus/morpheus/_lib/src/stages/write_to_file.cpp b/python/morpheus/morpheus/_lib/src/stages/write_to_file.cpp index 327c09df8b..75157cf174 100644 --- a/python/morpheus/morpheus/_lib/src/stages/write_to_file.cpp +++ b/python/morpheus/morpheus/_lib/src/stages/write_to_file.cpp @@ -17,13 +17,13 @@ #include "morpheus/stages/write_to_file.hpp" // IWYU pragma: associated -#include "mrc/segment/builder.hpp" -#include "mrc/segment/object.hpp" -#include "pymrc/node.hpp" - #include "morpheus/io/serializers.hpp" #include "morpheus/utilities/string_util.hpp" +#include +#include +#include + #include #include #include diff --git a/python/morpheus/morpheus/_lib/src/utilities/http_server.cpp b/python/morpheus/morpheus/_lib/src/utilities/http_server.cpp index c32a5708a9..5f60223f89 100644 --- a/python/morpheus/morpheus/_lib/src/utilities/http_server.cpp +++ b/python/morpheus/morpheus/_lib/src/utilities/http_server.cpp @@ -15,18 +15,15 @@ * limitations under the License. */ -// TODO(dagardner): add /health & /info endpoints - #include "morpheus/utilities/http_server.hpp" -#include "pymrc/utilities/function_wrappers.hpp" // for PyFuncWrapper - #include // for dispatch, make_address #include #include // for basic_socket_acceptor<>::executor_type #include // for basic_stream_socket #include -#include // for acceptor, endpoint, socket, +#include // for address +#include // for acceptor, endpoint, socket, #include // for socket_base::reuse_address, socket_base, socket_base::max_listen_connections #include // for strand, make_strand, operator== #include // for bind_front_handler, error_code, flat_buffer, tcp_stream @@ -35,26 +32,31 @@ #include // for flat_buffer #include #include // for tcp_stream -#include // for read_async, request, response, verb, write_async +#include // for read_async, response, write_async #include // for error, error::end_of_stream #include // for field, field::content_type #include -#include // for message, response, request -#include // for request_parser, parser -#include // for status, status::not_found -#include // for string_body, basic_string_body, basic_string_body<>::value_type -#include // for verb, operator<<, verb::unknown +#include // for message, response, request +#include // for request_parser, parser +#include // for status, status::not_found +#include // for verb, operator<<, verb::unknown #include -#include // for CHECK and LOG +#include // for CHECK and LOG +#include // for basic_json, json_ref #include #include // IWYU pragma: keep #include +#include // for PyFuncWrapper +#include // for cast_from_json -#include // for exception +#include // for max +#include // for exception +#include #include // needed for glog #include // for runtime_error, length_error #include // indirectly used by pybind11 casting #include // for move +// IWYU pragma: no_include // loosely based on the following examples: // https://www.boost.org/doc/libs/1_74_0/libs/beast/example/http/server/async/http_server_async.cpp @@ -130,8 +132,16 @@ class Session : public std::enable_shared_from_this if (request.target() == endpoint.m_url && request.method() == endpoint.m_method) { valid_request = true; - std::string body{request.body()}; - auto parse_status = (*endpoint.m_parser)(body); + std::tuple parse_status; + if (endpoint.m_request_handler != nullptr) + { + parse_status = (*endpoint.m_request_handler)(m_stream.socket().remote_endpoint(), request); + } + else + { + std::string body{request.body()}; + parse_status = (*endpoint.m_parser)(body); + } m_response->result(std::get<0>(parse_status)); m_response->set(http::field::content_type, std::get<1>(parse_status)); @@ -333,19 +343,53 @@ HttpServer::~HttpServer() } } +utilities::json_t request_headers_to_json(const tcp_endpoint_t& tcp_endpoint, const request_t& request) +{ + morpheus::utilities::json_t headers{{"method", request.method_string()}, + {"endpoint", request.target()}, + {"remote_address", tcp_endpoint.address().to_string()}, + {"remote_port", tcp_endpoint.port()}}; + + for (const auto& field : request) + { + headers[field.name_string()] = field.value(); + } + + return headers; +} + /****** HttpEndpointInterfaceProxy *************************/ using mrc::pymrc::PyFuncWrapper; namespace py = pybind11; std::shared_ptr HttpEndpointInterfaceProxy::init(pybind11::function py_parse_fn, std::string url, - std::string method) + std::string method, + bool include_headers) { - auto wrapped_parse_fn = PyFuncWrapper(std::move(py_parse_fn)); - payload_parse_fn_t payload_parse_fn = [wrapped_parse_fn = std::move(wrapped_parse_fn)](const std::string& payload) { + auto wrapped_parse_fn = PyFuncWrapper(std::move(py_parse_fn)); + request_handler_fn_t request_handler_fn = [include_headers, wrapped_parse_fn = std::move(wrapped_parse_fn)]( + const tcp_endpoint_t& tcp_endpoint, const request_t& request) { + std::string body{request.body()}; + std::unique_ptr headers{nullptr}; + if (include_headers) + { + headers = std::make_unique(std::move(request_headers_to_json(tcp_endpoint, request))); + } + py::gil_scoped_acquire gil; - auto py_payload = py::str(payload); - auto py_result = wrapped_parse_fn.operator()(py_payload); + auto py_payload = py::str(body); + pybind11::tuple py_result; + if (include_headers) + { + py::dict py_headers = mrc::pymrc::cast_from_json(*headers); + py_result = wrapped_parse_fn.operator()(py_payload, py_headers); + } + else + { + py_result = wrapped_parse_fn.operator()(py_payload); + } + on_complete_cb_fn_t cb_fn{nullptr}; if (!py_result[3].is_none()) { @@ -372,7 +416,7 @@ std::shared_ptr HttpEndpointInterfaceProxy::init(pybind11::functio std::move(cb_fn)); }; - return std::make_shared(std::move(payload_parse_fn), url, method); + return std::make_shared(std::move(request_handler_fn), std::move(url), method); } /****** HttpServerInterfaceProxy *************************/ @@ -425,11 +469,21 @@ void HttpServerInterfaceProxy::exit(HttpServer& self, self.stop(); } -HttpEndpoint::HttpEndpoint(payload_parse_fn_t payload_parse_fn, std::string url, std::string method) : - m_parser{std::make_shared(std::move(payload_parse_fn))}, +HttpEndpoint::HttpEndpoint(std::shared_ptr&& request_handler_fn, + std::shared_ptr&& payload_parse_fn, + std::string&& url, + const std::string& method) : + m_request_handler{std::move(request_handler_fn)}, + m_parser{std::move(payload_parse_fn)}, m_url{std::move(url)}, m_method{http::string_to_verb(method)} { + DCHECK(m_request_handler != nullptr || m_parser != nullptr) + << "Either request_handler_fn or payload_parse_fn must be provided"; + + DCHECK(m_request_handler == nullptr || m_parser == nullptr) + << "Only one of request_handler_fn or payload_parse_fn can be provided"; + if (m_method == http::verb::unknown) { throw std::runtime_error("Invalid method: " + method); @@ -441,6 +495,16 @@ HttpEndpoint::HttpEndpoint(payload_parse_fn_t payload_parse_fn, std::string url, } } +HttpEndpoint::HttpEndpoint(request_handler_fn_t request_handler_fn, std::string&& url, const std::string& method) : + HttpEndpoint{ + std::move(std::make_shared(std::move(request_handler_fn))), nullptr, std::move(url), method} +{} + +HttpEndpoint::HttpEndpoint(payload_parse_fn_t payload_parse_fn, std::string&& url, const std::string& method) : + HttpEndpoint{ + nullptr, std::move(std::make_shared(std::move(payload_parse_fn))), std::move(url), method} +{} + Listener::Listener(net::io_context& io_context, const std::string& bind_address, unsigned short port, diff --git a/python/morpheus/morpheus/_lib/src/utilities/table_util.cpp b/python/morpheus/morpheus/_lib/src/utilities/table_util.cpp index d6aa159b6d..690363d166 100644 --- a/python/morpheus/morpheus/_lib/src/utilities/table_util.cpp +++ b/python/morpheus/morpheus/_lib/src/utilities/table_util.cpp @@ -26,8 +26,8 @@ #include // for find, transform #include -#include // for back_insert_iterator, back_inserter -#include // for unique_ptr +#include // for back_insert_iterator, back_inserter +#include // for unique_ptr #include // needed for logging #include // for runtime_error diff --git a/python/morpheus/morpheus/_lib/stages/__init__.pyi b/python/morpheus/morpheus/_lib/stages/__init__.pyi index bb40f3916b..922c194deb 100644 --- a/python/morpheus/morpheus/_lib/stages/__init__.pyi +++ b/python/morpheus/morpheus/_lib/stages/__init__.pyi @@ -21,7 +21,8 @@ __all__ = [ "FileSourceStage", "FilterDetectionsStage", "FilterSource", - "HttpServerSourceStage", + "HttpServerControlMessageSourceStage", + "HttpServerMessageMetaSourceStage", "InferenceClientStage", "KafkaSourceStage", "PreallocateControlMessageStage", @@ -51,7 +52,10 @@ class FileSourceStage(mrc.core.segment.SegmentObject): class FilterDetectionsStage(mrc.core.segment.SegmentObject): def __init__(self, builder: mrc.core.segment.Builder, name: str, threshold: float, copy: bool, filter_source: morpheus._lib.common.FilterSource, field_name: str = 'probs') -> None: ... pass -class HttpServerSourceStage(mrc.core.segment.SegmentObject): +class HttpServerControlMessageSourceStage(mrc.core.segment.SegmentObject): + def __init__(self, builder: mrc.core.segment.Builder, name: str, bind_address: str = '127.0.0.1', port: int = 8080, endpoint: str = '/message', live_endpoint: str = '/live', ready_endpoint: str = '/ready', method: str = 'POST', live_method: str = 'GET', ready_method: str = 'GET', accept_status: int = 201, sleep_time: float = 0.10000000149011612, queue_timeout: int = 5, max_queue_size: int = 1024, num_server_threads: int = 1, max_payload_size: int = 10485760, request_timeout: int = 30, lines: bool = False, stop_after: int = 0, task_type: object = None, task_payload: object = None) -> None: ... + pass +class HttpServerMessageMetaSourceStage(mrc.core.segment.SegmentObject): def __init__(self, builder: mrc.core.segment.Builder, name: str, bind_address: str = '127.0.0.1', port: int = 8080, endpoint: str = '/message', live_endpoint: str = '/live', ready_endpoint: str = '/ready', method: str = 'POST', live_method: str = 'GET', ready_method: str = 'GET', accept_status: int = 201, sleep_time: float = 0.10000000149011612, queue_timeout: int = 5, max_queue_size: int = 1024, num_server_threads: int = 1, max_payload_size: int = 10485760, request_timeout: int = 30, lines: bool = False, stop_after: int = 0) -> None: ... pass class InferenceClientStage(mrc.core.segment.SegmentObject): diff --git a/python/morpheus/morpheus/_lib/stages/module.cpp b/python/morpheus/morpheus/_lib/stages/module.cpp index 266455177e..6fb855e0c5 100644 --- a/python/morpheus/morpheus/_lib/stages/module.cpp +++ b/python/morpheus/morpheus/_lib/stages/module.cpp @@ -38,8 +38,10 @@ #include // for Object, ObjectProperties #include // for MRC_CONCAT_STR #include // for multiple_inheritance +#include // IWYU pragma: keep #include // for arg, init, class_, module_, overload_cast, overload_... #include // for none, dict, str_attr +#include // IWYU pragma: keep #include // IWYU pragma: keep #include // for from_import, import #include // for trace_activity, decay_t @@ -230,11 +232,11 @@ PYBIND11_MODULE(stages, _module) py::arg("stride"), py::arg("column")); - py::class_, + py::class_>, mrc::segment::ObjectProperties, - std::shared_ptr>>( - _module, "HttpServerSourceStage", py::multiple_inheritance()) - .def(py::init<>(&HttpServerSourceStageInterfaceProxy::init), + std::shared_ptr>>>( + _module, "HttpServerMessageMetaSourceStage", py::multiple_inheritance()) + .def(py::init<>(&HttpServerSourceStageInterfaceProxy::init_meta), py::arg("builder"), py::arg("name"), py::arg("bind_address") = "127.0.0.1", @@ -255,6 +257,33 @@ PYBIND11_MODULE(stages, _module) py::arg("lines") = false, py::arg("stop_after") = 0); + py::class_>, + mrc::segment::ObjectProperties, + std::shared_ptr>>>( + _module, "HttpServerControlMessageSourceStage", py::multiple_inheritance()) + .def(py::init<>(&HttpServerSourceStageInterfaceProxy::init_cm), + py::arg("builder"), + py::arg("name"), + py::arg("bind_address") = "127.0.0.1", + py::arg("port") = 8080, + py::arg("endpoint") = "/message", + py::arg("live_endpoint") = "/live", + py::arg("ready_endpoint") = "/ready", + py::arg("method") = "POST", + py::arg("live_method") = "GET", + py::arg("ready_method") = "GET", + py::arg("accept_status") = 201u, + py::arg("sleep_time") = 0.1f, + py::arg("queue_timeout") = 5, + py::arg("max_queue_size") = 1024, + py::arg("num_server_threads") = 1, + py::arg("max_payload_size") = DefaultMaxPayloadSize, + py::arg("request_timeout") = 30, + py::arg("lines") = false, + py::arg("stop_after") = 0, + py::arg("task_type") = py::none(), + py::arg("task_payload") = py::none()); + py::class_, mrc::segment::ObjectProperties, std::shared_ptr>>( diff --git a/python/morpheus/morpheus/_lib/tests/messages/test_message_meta.cpp b/python/morpheus/morpheus/_lib/tests/messages/test_message_meta.cpp index 7214571f32..2357e62479 100644 --- a/python/morpheus/morpheus/_lib/tests/messages/test_message_meta.cpp +++ b/python/morpheus/morpheus/_lib/tests/messages/test_message_meta.cpp @@ -26,18 +26,22 @@ #include "morpheus/objects/tensor.hpp" // for Tensor #include "morpheus/types.hpp" // for RangeType -#include // for TestInfo, TEST_F -#include // for gil_scoped_release +#include // for TestInfo, TEST_F +#include // for gil_scoped_release +#include +#include #include // for cuda_stream_per_thread #include // for device_buffer #include // for int64_t #include // for operator/, path #include // for allocator, __shared_ptr_access, shared_ptr, make_shared +#include // for move #include // for vector using namespace morpheus; using namespace morpheus::test; +using namespace pybind11::literals; using TestMessageMeta = morpheus::test::TestMessages; // NOLINT(readability-identifier-naming) @@ -82,3 +86,25 @@ TEST_F(TestMessageMeta, CopyRangeAndSlicing) assert_eq_device_to_host(sliced_meta->get_info().get_column(0), sliced_expected_int); assert_eq_device_to_host(sliced_meta->get_info().get_column(1), sliced_expected_double); } + +TEST_F(TestMessageMeta, Issue1934) +{ + // Reproduce issue 1934 (https://github.com/nv-morpheus/Morpheus/issues/1934) + // The bug causes a segfault when calling `get_data_frame` on a message meta object + namespace py = pybind11; + py::gil_scoped_acquire gil; + + auto cudf_mod = py::module_::import("cudf"); + auto a_col = py::list(); + auto v1 = py::list(); + v1.append(py::str("a")); + a_col.attr("append")(std::move(v1)); + a_col.attr("append")(py::none()); + + auto df = cudf_mod.attr("DataFrame")(py::dict("a"_a = std::move(a_col))); + df.attr("drop")(0, "inplace"_a = true); + + auto msg = MessageMetaInterfaceProxy::init_python(std::move(df)); + + auto df_copy = MessageMetaInterfaceProxy::get_data_frame(*msg); +} diff --git a/python/morpheus/morpheus/_lib/tests/test_deserializers.cpp b/python/morpheus/morpheus/_lib/tests/test_deserializers.cpp index 128f8eae7b..6c0c0ffd2b 100644 --- a/python/morpheus/morpheus/_lib/tests/test_deserializers.cpp +++ b/python/morpheus/morpheus/_lib/tests/test_deserializers.cpp @@ -30,8 +30,8 @@ using namespace morpheus; namespace { -const std::string UserCols{",time,eventID,eventSource,eventName,z_loss\n"}; -const std::string DataRow{"0,1,2,test,test,5\n"}; +const std::string USER_COLS{",time,eventID,eventSource,eventName,z_loss\n"}; +const std::string DATA_ROW{"0,1,2,test,test,5\n"}; } // namespace TEST_CLASS(Deserializers); @@ -65,7 +65,7 @@ TEST_F(TestDeserializers, GetIndexColCountNoIdxSimilarName) for (const auto& col : not_id_cols) { // Build a list of column names with `col` as our first element followed by the columns in `UserCols` - std::string csv_data{col + UserCols + DataRow}; + std::string csv_data{col + USER_COLS + DATA_ROW}; auto options = cudf::io::csv_reader_options::builder(cudf::io::source_info{csv_data.c_str(), csv_data.size()}); auto table = cudf::io::read_csv(options.build()); @@ -83,7 +83,7 @@ TEST_F(TestDeserializers, GetIndexColCountIdx) for (const auto& col : id_cols) { // Build a list of column names with `col` as our first element followed by the columns in `UserCols` - std::string csv_data{col + UserCols + DataRow}; + std::string csv_data{col + USER_COLS + DATA_ROW}; auto options = cudf::io::csv_reader_options::builder(cudf::io::source_info{csv_data.c_str(), csv_data.size()}); auto table = cudf::io::read_csv(options.build()); @@ -95,8 +95,8 @@ TEST_F(TestDeserializers, GetIndexColCountIdx) TEST_F(TestDeserializers, GetIndexColCountValidNameInvalidType) { // Construct a csv with two rows, first row will contain the expected int id value second row will be a string - std::string data_rows{DataRow + "s,6,7,frog,toad,8\n"}; - std::string csv_data{"id" + UserCols + data_rows}; + std::string data_rows{DATA_ROW + "s,6,7,frog,toad,8\n"}; + std::string csv_data{"id" + USER_COLS + data_rows}; auto options = cudf::io::csv_reader_options::builder(cudf::io::source_info{csv_data.c_str(), csv_data.size()}); auto table = cudf::io::read_csv(options.build()); diff --git a/python/morpheus/morpheus/_lib/tests/test_dev_mem_info.cpp b/python/morpheus/morpheus/_lib/tests/test_dev_mem_info.cpp index 8b0a8b8a65..96c6afdf67 100644 --- a/python/morpheus/morpheus/_lib/tests/test_dev_mem_info.cpp +++ b/python/morpheus/morpheus/_lib/tests/test_dev_mem_info.cpp @@ -40,11 +40,11 @@ using namespace morpheus; namespace { -const TensorIndex Rows = 20; -const TensorIndex Cols = 5; -const auto Dtype = DType::create(); +const TensorIndex ROWS = 20; +const TensorIndex COLS = 5; +const auto DTYPE = DType::create(); -const auto ByteSize = Rows * Cols * Dtype.item_size(); +const auto BYTE_SIZE = ROWS * COLS * DTYPE.item_size(); template