SKILL.md
DeepStream SOP Inference Microservice Skill
This skill guides AI coding assistants in building, extending, and debugging the NVIDIA DeepStream SOP (Standard Operating Procedure) Inference Microservice — a GPU-accelerated pipeline for temporal action detection and VLM-based SOP compliance monitoring on industrial video feeds.
Reference repository: https://github.com/NVIDIA/sop-monitoring-blueprints/tree/main/microservices/sop-inference-bp Local reference code: sop-inference-bp/ directory (from a local clone of the repository)
Models
Model-agnostic at both inference stages — swap via env var (and Triton dir for GEBD).
| Stage | Role | Model class | Default | Swap via |
|---|---|---|---|---|
| Stage 1 (CV) | Per-frame boundary scoring → chunk segmentation | Generic Event Boundary Detection (GEBD) | DDM (MCG-NJU/DDM) via Triton Python backend | Replace tritonmodelrepo/<model>/ + DDMMODELPATH (§ 5) |
| Stage 3 (VLM) | Per-chunk action classification | Vision-language model via vLLM | Cosmos Reason 1 7B (Reason 2 also supported) | Set VLLMMODELPATH to a different HF ID or local path |
"GEBD" = swappable Stage-1 slot; "DDM" = the default architecture (terms used interchangeably).
Chunking is selectable per request (§ 2): default ddm-net uses GEBD; uniform produces fixed-length chunks and bypasses Stage-1 GEBD (§ 3, § 6). DDM temporal window is configurable via FRAMESPERSIDE / SEQUENCE_BATCH (§ 4, § 5), with optional TensorRT (§ 5).
Architecture Overview
Runs in a Docker container (nvds-action-sop) alongside a Kafka container. Full diagram: [references/soparchitecture.svg](references/soparchitecture.svg).
Data flow through the 4-stage SOPVideoProcessor pipeline (per-request):
Input Sources Docker Container: nvds-action-sop
───────────── ──────────────────────────────────────────────────
Video Files ──┐ FastAPI Server (port 8300)
RTSP Streams ─┤── base64/ ├─ /v1/chat/completions → SOPProcessManager
Basler Camera ┘ file/rtsp/ │
camera │ ModelInitializer: VLM first, then DDM dummy pipeline
│ 4 Thread Pools: cv(32), clip(32), vlm(64), vlm_req(64)
│
▼ SOPVideoProcessor (per-request)
┌────────────────────────────────────────────────┐
│ Stage 1: DeepStream Pipeline (GPU) │
│ Source → nvstreammux → tee1 │
│ ├─[inference] queue1 → nvdspreprocess │
│ │ → nvinferserver (Triton CAPI + DDM) │
│ │ → InferOutputTensorParser → score_queue │
│ ├─[frames] queue3 → nvvideoconvert │
│ │ → capsfilter → appsink │
│ │ → DecodedFrameRetriever → frame_queue │
│ └─[RTSP out] queue → convert → H.264 enc │ (optional, § 18)
│ → rtppay → udpsink → RTSPServer (§ 18) │ opt-in only
│ │ boundary scores │
│ ▼ │
│ Stage 2: Clip Post-Process │
│ Boundary detection → chunk segmentation │
│ │ video frames + timestamps │
│ ▼ │
│ Stage 3: VLM Inference │
│ Embedded vLLM (Cosmos Reason 1/2) │
│ Frame sampling at VLM_FPS → classification │
│ │ action labels │
│ ▼ │
│ Stage 4: SOP Checker │
│ Sequence validation → missing/misordered │
│ │ chunk results │
│ ▼ │
│ final_queue │
└────────────────────────────────────────────────┘
│
Output ▼
────── ┌─────────────────┐
SSE Stream (chat.completion.chunk) │ Kafka Messages │
Non-streaming (chat.completion) │ (JSON/Protobuf) │
Prometheus metrics (/v1/metrics) └────────┬────────┘
▼
Docker Container: kafka
(apache/kafka:3.7.0)
Section Index
Each section is a standalone file in references/ — load only what your task needs.
| § | File | Responsibility |
|---|---|---|
| 1 | [skill01fastapiendpoints.md](references/skill01fastapiendpoints.md) |
FastAPI endpoints, server init, Prometheus metrics |
| 2 | [skill02pydanticschemas.md](references/skill02pydanticschemas.md) |
Request/response Pydantic models (api_types.py) |
| 3 | [skill03deepstreampipeline.md](references/skill03deepstreampipeline.md) |
DeepStream pyservicemaker pipeline, tensor parser, dummy pipeline |
| 4 | [skill04configtemplates.md](references/skill04configtemplates.md) |
nvdspreprocess / nvinferserver config templates + rendering |
| 5 | [skill05tritonddmmodel.md](references/skill05tritonddmmodel.md) |
Triton model repo, config.pbtxt, model.py, ddm_net.py |
| 5b | [skill05bcustompostprocess.md](references/skill05bcustompostprocess.md) |
C++ postprocess plugin, Makefile, IOptions API |
| 6 | [skill06sopprocessmanager.md](references/skill06sopprocessmanager.md) |
SOPProcessManager, SOPVideoProcessor, VLLMInference, Kafka |
| 6b | [skill06bsopchecker.md](references/skill06bsopchecker.md) |
SOP sequence and checker compliance: MissingNumberDetector, SopCheckerCache, SopCheckerRequest/Response |
| 7 | [skill07ssestreaming.md](references/skill07ssestreaming.md) |
SSE generator, stream response formatting, dummy test mode |
| 8 | [skill08baslercamera.md](references/skill08baslercamera.md) |
Basler camera support, Pylon SDK, emulation, formats |
| 9 | [skill09dockerbuilddeploy.md](references/skill09dockerbuilddeploy.md) |
Docker build, deploy, .env configuration |
| 10 | [skill10testsuite.md](references/skill10testsuite.md) |
Test suite coverage, assertions, running tests |
| 11 | [skill11envvariables.md](references/skill11envvariables.md) |
All environment variables reference |
| 12 | [skill12evaluationworkflow.md](references/skill12evaluationworkflow.md) |
End-to-end eval workflow: static checks, build, launch, tests, API/camera/Kafka checks, report |
| 13 | [skill13verificationcurl.md](references/skill13verificationcurl.md) |
Verification steps and curl examples |
| 14 | [skill14implementationchecklist.md](references/skill14implementationchecklist.md) |
Implementation checklist: file copy list, generated files, Docker prereqs, verification |
| 15 | [skill15latencymeasurement.md](references/skill15latencymeasurement.md) |
TTFC and C2C latency measurement for file input via SSE streaming |
| 16 | [skill16messageschema.md](references/skill16messageschema.md) |
Kafka message schema selection (JSON default vs NvProtoSchema) and extending messages with custom data |
| 17 | [skill17cameralatencymeasurement.md](references/skill17cameralatencymeasurement.md) |
Camera / live-stream chunk_e2e latency measurement using internal pipeline timestamps |
| 18 | [skill18rtspstreamingoutput.md](references/skill18rtspstreamingoutput.md) |
OPT-IN RTSP streaming output: tee1-tap re-stream, RTSPStreamingServer, SW_ENCODER toggle. Generate only when user explicitly requests RTSP |
For end-to-end evaluation, read § 12 first; load build/test/curl/latency/camera/Kafka as needed.
§ 18 is opt-in — generate only when the user explicitly requests RTSP output; otherwise skip § 18 and the RTSP_* rules below.
Key Files Map
The full source-to-target file mapping lives in [skill14implementationchecklist.md](references/skill14implementationchecklist.md):
- Files copied verbatim from
references/(non-trivial algorithms — cycle
detection, qwenvlutils preprocessing, DeepStream IOptions API, protobuf sources) with the rationale per file.
- Files copied as adaptable templates (Dockerfile, compose.yaml, Triton
config and model.py, ddm_net.py, Pylon emulation config, etc.).
- Files generated from skill sections — each annotated with the Critical
Rules below that the generation must follow exactly.
- Docker build prerequisites and post-build verification checklist.
Config files (nvdspreprocesstemplate.txt, nvdsinferencetemplate.txt, vlm_prompts.txt) are used as-is from configs/.
When skill06b is loaded, read configs/actions.json from the project root and run the § 6b-G generation workflow to produce nvdsactiondetector/missingnumber_detector.py. If configs/actions.json is absent or invalid, fall back to copying the reference file.
Critical Rules
Each rule's full detail lives in the linked
skillNN*.mdreference file.
| Tag | Rule summary | Details in |
|---|---|---|
MANAGERINITIN_MAIN |
SOPProcessManager init in main() before uvicorn.run() — not inside lifespan() |
skill01fastapi_endpoints.md |
NAMED_KWARGS |
createvideoprocessor() uses named kwargs; camera args as separate kwargs |
skill06sopprocessmanager.md |
LIVEREQUIRESSTREAM_TRUE |
stream: true required for live inputs (RTSP / camera) |
skill08basler_camera.md |
VLMDISABLEDDISABLESSOPCHECKER |
DISABLEVLMINFERENCE=true auto-disables SOP checker at import |
skill06sopprocessmanager.md |
CHUNKPARAMSMAX_LENGTH |
ChunkParams.maxlengthsec = 10s internal; 60s API default |
skill06sopprocessmanager.md |
VLMWARMUPBEFORE_DDM |
ModelInitializer: VLM warmup FIRST, then CV dummy pipeline |
skill06sopprocessmanager.md |
VLMWARMUP3_FRAMES |
VLM warmup needs 3 frames (torch.zeros) — Qwen3VL hangs on < 3 |
skill06sopprocessmanager.md |
THREADPOOLSIZES |
4 thread pools: cv(32), clip(32), vlminference(64), vlmrequest(64) |
skill06sopprocessmanager.md |
MEDIAINFOPYMEDIAINFO |
Media info via pymediainfo; live sources set fps=30/duration=inf directly |
skill06sopprocessmanager.md |
CAMERAEMULATIONPYLON_CAMEMU |
PYLON_CAMEMU=1 for camera emulation (serial 0815-0000) |
skill08basler_camera.md |
DEEPSTREAMLIBHIDE |
DeepStream lib hide trick: rename lib → lib.tmp during gst-plugin-pylon build | skill08basler_camera.md |
VLMREALGPU_FRAMES |
VLM uses real GPU frames via DecodedFrameRetriever; never torch.zeros for inference |
skill06sopprocessmanager.md |
BUFFERRETRIEVERSTATIC_BASE |
DecodedFrameRetriever MUST inherit BufferRetriever statically via super().init(); runtime class.bases mutation hangs pipeline.attach() |
skill06sopprocessmanager.md |
FRAMERETRIEVERPRIORITY |
createinferencepipeline: frameretriever= kwarg takes priority over framequeue |
skill03deepstream_pipeline.md |
MUXORIGINALRESOLUTION |
nvstreammux uses original resolution (not 224); pass muxwidth/muxheight from getmediainfo() (probe live RTSP for non-camera inputs; camera path unaffected) |
skill03deepstreampipeline.md, skill06sopprocess_manager.md |
FILEURINODOUBLEPREFIX |
createinferencepipeline file source: check file_path.startswith("file://") before prepending — API passes file:// URLs directly |
skill03deepstream_pipeline.md |
CLEANUPONDISCONNECT |
Pipeline cleanup on client disconnect via triggerstopprocessors in try/finally |
skill07sse_streaming.md |
UNIFIEDCLIPPOST_PROCESS |
Unified clippostprocess() for file + live; stop() puts None in scorequeue |
skill06sopprocessmanager.md |
ABORTINFLIGHTVLM |
Abort in-flight VLM requests on stop() via llm.abort(req_id) |
skill06sopprocessmanager.md |
LOGGEREXPORTGET_LOGGER |
dslogger.py must export getlogger |
skill06sopprocessmanager.md |
KAFKAUSECREATE_PRODUCER |
Kafka: use create_producer() from messager.py; no Messager class |
skill06sopprocessmanager.md |
USERPROMPTPRIORITY |
User request text takes priority over VLMPROMPTPATH file; {"type":"text"} in the request overrides the config-file prompt |
skill06sopprocessmanager.md |
EVALUSECONFIG_PROMPT |
Eval/latency requests omit request text by default so the VLM uses VLMPROMPTPATH |
skill12evaluationworkflow.md, skill13verificationcurl.md, skill15latencymeasurement.md, skill17cameralatency_measurement.md |
CHUNKSCHEMAFIELD_NAMES |
Chunk schema: chunkidx, cvboundaryscore, checkerresult; summary chunk_idx=-1 |
skill06sopprocessmanager.md |
SEQUENTIALFRAMEDRAIN |
Drain decodedframequeue (FIFO, shared across chunks) in a SINGLE thread and submit VLM per chunk incrementally; parallel drain steals frames → 0-frame chunks / wrong VLM input |
skill06sopprocessmanager.md |
WALLCLOCKBEFORE_GPU |
DecodedFrameRetriever.consume(): capture wallclockentry = time.time() BEFORE GPU dlpack; queue 3-tuple (timestamp, wallclockentry, tensor) |
skill06sopprocessmanager.md, skill17cameralatencymeasurement.md |
CHUNKE2EPIPELINE_TIMESTAMPS |
Write pipelinechunkendtimestamp (last frame wallclock) and pipelinevlmreadytimestamp (tme2e.now()) into chunk_info for camera latency (§ 17) |
skill06sopprocessmanager.md, skill17cameralatencymeasurement.md |
VLMINFERENCEREQUIRED_KWARGS |
Every VLLMInference.inference() call must pass videofps, systemprompt, maxcompletiontokens |
skill06sopprocessmanager.md |
UNIFORMCHUNKINGBYPASSES_DDM |
chunkingoptions.algorithm="uniform" → fixed-length chunks; createinferencepipeline(uniformchunk=True) skips DDM but keeps tee1 fanout; Stage 2 uses uniformclippost_process |
skill02pydanticschemas.md, skill03deepstreampipeline.md, skill06sopprocessmanager.md |
DDMTEMPORALCONFIGURABLE |
SLIDINGWINDOWSSIZE = 2*FRAMESPERSIDE + SEQUENCE_BATCH rendered into preprocess/nvinferserver (no hard-coded 18); Triton config.pbtxt sequence dim -1 |
skill04configtemplates.md, skill05tritonddm_model.md |
DDMTRTOPTIONAL_PATH |
DDMTRTOPTIMIZATION=true runs DDM via TensorRT (per-thread contexts, fixed batch = SEQUENCE_BATCH); PyTorch fallback; never both. PyTorch is default |
skill05tritonddmmodel.md |
DDMTRTSTREAM_ORDERING |
DDMTensorRTEngine.infer(): waitstream(current) → executeasync_v3 → torch.cuda.synchronize(device) (NOT per-stream). Per-stream sync leaves TRT aux-stream work in flight → gst-CV SIGSEGV (NVBug 6289256) |
skill05tritonddmmodel.md |
METADATALICENSEFROM_FILE |
/v1/metadata reads licenseInfo from DSSOPLICENSEPATH (default /opt/nvidia/nvdssop/license.txt); never hard-code license text |
skill01fastapi_endpoints.md |
CAMERAEMULATIONFRAMES_RGB |
Pylon emulation PNGs must be explicit 3-channel RGB (matches Emulation_0815-0000.pfs PixelFormat=RGB8Packed); generate via nvvideoconvert ! videoconvert ! "video/x-raw,format=RGB" ! pngenc |
skill08basler_camera.md |
COMPOSEENVPASSTHROUGH |
docker compose only substitutes ${VAR} references; every runtime env var must be explicitly listed under environment: to reach the container. |
skill09dockerbuilddeploy.md |
The four
RTSP_*rules below apply **only when the optional RTSP streaming-output feature
(§ 18) is requested**. They do not apply to the default build — skip them if the user did
not ask for RTSP output.
| RTSPOUTPUTTAPSTEE1 | RTSP output branch links from the existing tee1 (added after the main inference link) only when rtspport is present. | skill18rtspstreamingoutput.md | | RTSPLEAKYQUEUETINY | RTSP branch queue must be leaky=2 + tiny cap (max-size-buffers=2) to prevent backpressure and NVMM pool exhaustion. | skill18rtspstreamingoutput.md | | RTSPKEYINTMAX30 | RTSP H.264 encoder must set key-int-max=30 (and B-frames disabled) to allow downstream seeking. | skill18rtspstreamingoutput.md | | RTSPENCODERFALLBACK | Select software/hardware H.264 encoder based on SWENCODER with MJPEG fallback. | skill18rtspstreaming_output.md |