| 名称 | “deepstream-sop” |
| 描述 | > 使用此技能用于构建、部署、评估、调试或测量 DeepStream SOP 推理微服务的延迟——这是一种 GPU 加速的 FastAPI 服务,通过事件边界检测 (GEBD) 和 VLM 分类来检测操作员是否按顺序执行装配线步骤。即使用户没有明确提及也会触发:验证操作员步骤顺序,检测缺失/乱序的 SOP 步骤,对工厂/工作单元视频进行程序合规性评分,在工业摄像头上运行基于 VLM 的 SOP 检查,或调用带有文件、RTSP 或 Basler 相机的 /v1/chat/completions。同样适用于其内部组件:SOPVideoProcessor、DeepStream GEBD 模型(例如 DDM)通过 Triton CAPI、nvds_custom_postprocess、Cosmos Reason 1/2 vLLM、SSE 流式、Kafka NvProto/JSON 输出、Basler/Pylon 相机 + 仿真、Docker compose、分块级延迟。请勿为通用 DeepStream 管线、目标检测/跟踪、NIM 导入或视频摘要触发。 owner: “windy@nvidia.com” service: “deepstream-sop” |
| 版本 | “1.0.0” |
| 开源协议 | “CC-BY-4.0 AND Apache-2.0” reviewed: “2026-04-08” metadata: |
| 作者 | “Wind Yuan windy@nvidia.com” tags: - deepstream - sop - vlm - triton - gpu languages: - python frameworks: - deepstream - triton - fastapi domain: video-analytics |
DeepStream SOP 推理微服务技能
本技能用于指导 AI 编程助手构建、扩展和调试 NVIDIA DeepStream SOP(标准操作流程)推理微服务 —— 面向工业视频流的、基于 GPU 加速的时序动作检测与 VLM 驱动的 SOP 合规性监控管线。
参考仓库: https://github.com/NVIDIA/sop-monitoring-blueprints/tree/main/microservices/sop-inference-bp
本地参考代码: sop-inference-bp/ 目录(来自仓库的本地克隆)
模型
两个推理阶段均不依赖具体模型 —— 可通过环境变量(以及 GEBD 对应的 Triton 目录)进行替换。
| 阶段 | 作用 | 模型类型 | 默认值 | 替换方式 |
|---|---|---|---|---|
| 阶段 1 (CV) | 逐帧边界评分 → 块划分 | 通用事件边界检测 (GEBD) | DDM(MCG-NJU/DDM)通过 Triton Python 后端 | 替换 triton_model_repo/<model>/ 和 DDM_MODEL_PATH(见 §5) |
| 阶段 3 (VLM) | 每个块的行动分类 | 基于 LLM 的视觉语言模型(vLLM 运行) | Cosmos Reason 1 7B(同时支持 Reason 2) | 将 VLLM_MODEL_PATH 设置为不同的 HF ID 或本地路径 |
“GEBD”是可替换的阶段 1 槽;“DDM”是默认架构(术语可互换使用)。
分块方式在每个请求中可选择(见§2):默认 ddm-net 使用 GEBD;uniform 产生固定长度块,并 绕过阶段 1 的 GEBD(见§3、§6)。DDM 的时间窗口可通过 FRAMES_PER_SIDE / SEQUENCE_BATCH(见§4、§5)配置,并支持可选的 TensorRT(见§5)。
架构总览
在 Docker 容器(nvds-action-sop)中运行,并旁边运行 Kafka 容器。完整图示请见 references/sop_architecture.svg。
数据流经 4 阶段 SOPVideoProcessor 管线(每个请求一个实例):
输入源 Docker 容器: nvds-action-sop
───────────── ──────────────────────────────────────────────────
视频文件 ──┐ FastAPI 服务器 (端口 8300)
RTSP 流 ─┤── base64/ ├─ /v1/chat/completions → SOPProcessManager
Basler 相机 ┘ file/rtsp/ │
camera │ ModelInitializer: 先加载 VLM,再加载 DDM 哑管线
│ 4 个线程池: cv(32), clip(32), vlm(64), vlm_req(64)
│
▼ SOPVideoProcessor (每个请求一个实例)
┌────────────────────────────────────────────────┐
│ 阶段 1: DeepStream 管线 (GPU) │
│ 源 → nvstreammux → tee1 │
│ ├─[推理] queue1 → nvdspreprocess │
│ │ → nvinferserver (Triton CAPI + DDM) │
│ │ → InferOutputTensorParser → score_queue │
│ ├─[帧] queue3 → nvvideoconvert │
│ │ → capsfilter → appsink │
│ │ → DecodedFrameRetriever → frame_queue │
│ └─[RTSP 输出] queue → convert → H.264 编码 │ (可选,见 §18)
│ → rtppay → udpsink → RTSPServer (§18) │ 按需启用
│ │ 边界分数 │
│ ▼ │
│ 阶段 2: 剪辑后处理 │
│ 边界检测 → 块划分 │
│ │ 视频帧 + 时间戳 │
│ ▼ │
│ 阶段 3: VLM 推理 │
│ 内嵌 vLLM (Cosmos Reason 1/2) │
│ 以 VLM_FPS 进行帧采样 → 分类 │
│ │ 动作标签 │
│ ▼ │
│ 阶段 4: SOP 校验器 │
│ 顺序验证 → 缺少/错序检测 │
│ │ 块结果 │
│ ▼ │
│ final_queue │
└────────────────────────────────────────────────┘
│
输出 ▼
────── ┌─────────────────┐
SSE 流 (chat.completion.chunk) │ Kafka 消息 │
非流式 (chat.completion) │ (JSON/Protobuf) │
Prometheus 指标 (/v1/metrics) └────────┬────────┘
▼
Docker 容器: kafka
(apache/kafka:3.7.0)
章节索引
每个章节都是 references/ 下的独立文件 —— 仅加载你的任务所需的章节。
| § | 文件 | 职责 |
|---|---|---|
| 1 | skill_01_fastapi_endpoints.md |
FastAPI 端点、服务器初始化、Prometheus 指标 |
| 2 | skill_02_pydantic_schemas.md |
请求/响应 Pydantic 模型(api_types.py) |
| 3 | skill_03_deepstream_pipeline.md |
DeepStream pyservicemaker 管线、张量解析器、哑管线 |
| 4 | skill_04_config_templates.md |
nvdspreprocess / nvinferserver 配置模板 + 渲染 |
| 5 | skill_05_triton_ddm_model.md |
Triton 模型仓库、config.pbtxt、model.py、ddm_net.py |
| 5b | skill_05b_custom_postprocess.md |
C++ 后处理插件、Makefile、IOptions API |
| 6 | skill_06_sop_process_manager.md |
SOPProcessManager、SOPVideoProcessor、VLLMInference、Kafka |
| 6b | skill_06b_sop_checker.md |
SOP 顺序与校验合规:MissingNumberDetector、SopCheckerCache、SopCheckerRequest/Response |
| 7 | skill_07_sse_streaming.md |
SSE 生成器、流式响应格式、哑测试模式 |
| 8 | skill_08_basler_camera.md |
Basler 相机支持、Pylon SDK、仿真、格式 |
| 9 | skill_09_docker_build_deploy.md |
Docker 构建、部署、.env 配置 |
| 10 | skill_10_test_suite.md |
测试套件覆盖率、断言、运行测试 |
| 11 | skill_11_env_variables.md |
所有环境变量参考 |
| 12 | skill_12_evaluation_workflow.md |
端到端评估工作流:静态检查、构建、启动、测试、API/相机/Kafka 检查、报告 |
| 13 | skill_13_verification_curl.md |
验证步骤和 curl 示例 |
| 14 | skill_14_implementation_checklist.md |
实现清单:文件复制列表、生成文件、Docker 前置条件、验证 |
| 15 | skill_15_latency_measurement.md |
通过 SSE 流式测量文件输入的 TTFC 和 C2C 延迟 |
| 16 | skill_16_message_schema.md |
Kafka 消息模式选择(默认 JSON vs NvProtoSchema)以及扩展自定义数据的消息 |
| 17 | skill_17_camera_latency_measurement.md |
相机/直播流的块级端到端延迟测量,使用内部管线时间戳 |
| 18 | skill_18_rtsp_streaming_output.md |
可选 RTSP 流式输出:tee1 分接头重流、RTSPStreamingServer、SW_ENCODER 切换。仅当用户明确请求 RTSP 时才生成。 |
若要端到端评估,请先阅读 §12;然后根据需要加载构建/测试/curl/延迟/相机/Kafka 等章节。
§18 为可选 —— 仅当用户明确请求 RTSP 输出时生成;否则跳过 §18 及以下 RTSP_* 规则。
关键文件映射
完整的源文件到目标文件的映射表见
skill_14_implementation_checklist.md:
- 从
references/原样复制的文件(非平凡算法——循环检测、qwen_vl_utils 预处理、DeepStreamIOptionsAPI、protobuf 源码),并说明每个文件的理由。 - 作为可适配模板复制的文件(Dockerfile、compose.yaml、Triton 配置和
model.py、ddm_net.py、Pylon 仿真配置等)。 - 由技能章节生成的文件 —— 每项都标注了必须精确遵循的下方关键规则。
- Docker 构建前置条件和构建后验证清单。
配置文件(nvds_preprocess_template.txt、nvds_inference_template.txt、
vlm_prompts.txt)是从 configs/ 按原样使用的。
当加载 skill_06b 时,从项目根目录读取 configs/actions.json,并按
§6b-G 生成工作流生成 nvds_action_detector/missing_number_detector.py。
如果 configs/actions.json 不存在或无效,则回退到复制参考文件。
关键规则
每条规则的完整详情位于所链接的
skill_NN_*.md参考文件中。
| 标签 | 规则摘要 | 详情位置 |
|---|---|---|
MANAGER_INIT_IN_MAIN |
SOPProcessManager 在 main() 中初始化,需在 uvicorn.run() 之前 —— 不能在 lifespan() 里 |
skill_01_fastapi_endpoints.md |
NAMED_KWARGS |
create_video_processor() 使用命名关键字参数;相机参数作为单独关键字参数 |
skill_06_sop_process_manager.md |
LIVE_REQUIRES_STREAM_TRUE |
实时输入 (RTSP/相机) 必须 stream: true |
skill_08_basler_camera.md |
VLM_DISABLED_DISABLES_SOP_CHECKER |
DISABLE_VLM_INFERENCE=true 在导入时自动禁用 SOP 校验器 |
skill_06_sop_process_manager.md |
CHUNK_PARAMS_MAX_LENGTH |
ChunkParams.max_length_sec 内部为 10 秒;API 默认 60 秒 |
skill_06_sop_process_manager.md |
VLM_WARMUP_BEFORE_DDM |
ModelInitializer: 先做 VLM 预热,再做 CV 哑管线 |
skill_06_sop_process_manager.md |
VLM_WARMUP_3_FRAMES |
VLM 预热需要 3 帧(torch.zeros)—— Qwen3VL 在少于 3 帧时会挂起 |
skill_06_sop_process_manager.md |
THREAD_POOL_SIZES |
4 个线程池:cv(32)、clip(32)、vlm_inference(64)、vlm_request(64) |
skill_06_sop_process_manager.md |
MEDIA_INFO_PYMEDIAINFO |
媒体信息通过 pymediainfo;实时源直接设置 fps=30/duration=inf |
skill_06_sop_process_manager.md |
CAMERA_EMULATION_PYLON_CAMEMU |
PYLON_CAMEMU=1 开启相机仿真(序列号 0815-0000) |
skill_08_basler_camera.md |
DEEPSTREAM_LIB_HIDE |
DeepStream 库隐藏技巧:在 gst-plugin-pylon 构建期间将 lib 重命名为 lib.tmp | skill_08_basler_camera.md |
VLM_REAL_GPU_FRAMES |
VLM 使用通过 DecodedFrameRetriever 获取的真实 GPU 帧;推理时绝不使用 torch.zeros |
skill_06_sop_process_manager.md |
BUFFER_RETRIEVER_STATIC_BASE |
DecodedFrameRetriever 必须静态继承 BufferRetriever,通过 super().__init__();运行时的 __class__.__bases__ 变更会导致 pipeline.attach() 挂起 |
skill_06_sop_process_manager.md |
FRAME_RETRIEVER_PRIORITY |
create_inference_pipeline: frame_retriever= 关键字参数优先于 frame_queue |
skill_03_deepstream_pipeline.md |
MUX_ORIGINAL_RESOLUTION |
nvstreammux 使用原始分辨率(不是 224);从 get_media_info() 传入 mux_width/mux_height(对非相机输入探测实时 RTSP;相机路径不受影响) |
skill_03_deepstream_pipeline.md, skill_06_sop_process_manager.md |
FILE_URI_NO_DOUBLE_PREFIX |
create_inference_pipeline 文件源:在添加前缀前检查 file_path.startswith("file://") — API 直接传递 file:// URL |
skill_03_deepstream_pipeline.md |
CLEANUP_ON_DISCONNECT |
客户端断开时管线清理,通过 trigger_stop_processors 在 try/finally 中实现 |
skill_07_sse_streaming.md |
UNIFIED_CLIP_POST_PROCESS |
文件和实时输入共用 clip_post_process();stop() 向 _score_queue 放入 None |
skill_06_sop_process_manager.md |
ABORT_INFLIGHT_VLM |
stop() 时通过 llm.abort(req_id) 中止进行中的 VLM 请求 |
skill_06_sop_process_manager.md |
LOGGER_EXPORT_GET_LOGGER |
ds_logger.py 必须导出 get_logger |
skill_06_sop_process_manager.md |
KAFKA_USE_CREATE_PRODUCER |
Kafka:使用来自 messager.py 的 create_producer();没有 Messager 类 |
skill_06_sop_process_manager.md |
USER_PROMPT_PRIORITY |
用户请求文本优先于 VLM_PROMPT_PATH 文件;请求中的 {"type":"text"} 覆盖配置文件中的提示词 |
skill_06_sop_process_manager.md |
EVAL_USE_CONFIG_PROMPT |
评估/延迟请求默认省略请求文本,使 VLM 使用 VLM_PROMPT_PATH |
skill_12_evaluation_workflow.md, skill_13_verification_curl.md, skill_15_latency_measurement.md, skill_17_camera_latency_measurement.md |
CHUNK_SCHEMA_FIELD_NAMES |
块模式:chunk_idx、cv_boundary_score、checker_result;摘要 chunk_idx=-1 |
skill_06_sop_process_manager.md |
SEQUENTIAL_FRAME_DRAIN |
在单个线程中按顺序排空 decoded_frame_queue(FIFO,跨块共享)并逐个提交 VLM;并行排空会偷帧 → 导致 0 帧块或错误的 VLM 输入 |
skill_06_sop_process_manager.md |
WALL_CLOCK_BEFORE_GPU |
DecodedFrameRetriever.consume(): 在 GPU dlpack 之前捕获 wall_clock_entry = time.time();将 3 元组 (timestamp, wall_clock_entry, tensor) 放入队列 |
skill_06_sop_process_manager.md, skill_17_camera_latency_measurement.md |
CHUNK_E2E_PIPELINE_TIMESTAMPS |
将 pipeline_chunk_end_timestamp(最后一帧的墙钟时间)和 pipeline_vlm_ready_timestamp(tm_e2e.now())写入 chunk_info 用于相机延迟测量(§17) |
skill_06_sop_process_manager.md, skill_17_camera_latency_measurement.md |
VLM_INFERENCE_REQUIRED_KWARGS |
每次 VLLMInference.inference() 调用必须传递 video_fps、system_prompt、max_completion_tokens |
skill_06_sop_process_manager.md |
UNIFORM_CHUNKING_BYPASSES_DDM |
chunking_options.algorithm="uniform" → 固定长度块;create_inference_pipeline(uniform_chunk=True) 跳过 DDM 但保留 tee1 分叉;阶段 2 使用 uniform_clip_post_process |
skill_02_pydantic_schemas.md, skill_03_deepstream_pipeline.md, skill_06_sop_process_manager.md |
DDM_TEMPORAL_CONFIGURABLE |
SLIDING_WINDOWS_SIZE = 2*FRAMES_PER_SIDE + SEQUENCE_BATCH 渲染进 preprocess/nvinferserver(没有硬编码 18);Triton config.pbtxt 序列维度为 -1 |
skill_04_config_templates.md, skill_05_triton_ddm_model.md |
DDM_TRT_OPTIONAL_PATH |
DDM_TRT_OPTIMIZATION=true 通过 TensorRT 运行 DDM(每线程上下文,固定 batch = SEQUENCE_BATCH);否则使用 PyTorch;两者不会同时启用。PyTorch 是默认配置 |
skill_05_triton_ddm_model.md |
DDM_TRT_STREAM_ORDERING |
DDMTensorRTEngine.infer(): wait_stream(current) → execute_async_v3 → torch.cuda.synchronize(device)(不是每流同步)。每流同步会使 TRT 辅助流的工作尚未完成 → 导致 gst-CV SIGSEGV(NVBug 6289256) |
skill_05_triton_ddm_model.md |
METADATA_LICENSE_FROM_FILE |
/v1/metadata 从 DS_SOP_LICENSE_PATH(默认 /opt/nvidia/nvds_sop/license.txt)读取 licenseInfo;绝不硬编码许可证文本 |
skill_01_fastapi_endpoints.md |
CAMERA_EMULATION_FRAMES_RGB |
Pylon 仿真 PNG 必须为显式 3 通道 RGB(与 Emulation_0815-0000.pfs PixelFormat=RGB8Packed 匹配);通过 nvvideoconvert ! videoconvert ! "video/x-raw,format=RGB" ! pngenc 生成 |
skill_08_basler_camera.md |
COMPOSE_ENV_PASSTHROUGH |
docker compose 只替换 ${VAR} 引用;每个运行时环境变量必须显式列在 environment: 下才能进入容器 |
skill_09_docker_build_deploy.md |
下面四个
RTSP_*规则仅在使用可选 RTSP 流式输出功能(§18)时适用。不要用于默认构建 —— 如果用户没有要求 RTSP 输出,请跳过这些规则。
| RTSP_OUTPUT_TAPS_TEE1 | 仅当存在 rtsp_port 时,RTSP 输出分支才从现有 tee1(在主推理链路之后添加的)连接。 | skill_18_rtsp_streaming_output.md |
| RTSP_LEAKY_QUEUE_TINY | RTSP 分支队列必须为 leaky=2 + 极小容量(max-size-buffers=2),以防止背压和 NVMM 池耗尽。 | skill_18_rtsp_streaming_output.md |
| RTSP_KEYINT_MAX_30 | RTSP H.264 编码器必须设置 key-int-max=30(并禁用 B 帧),以允许下游 seek。 | skill_18_rtsp_streaming_output.md |
| RTSP_ENCODER_FALLBACK | 根据 SW_ENCODER 选择软件/硬件 H.264 编码器,并提供 MJPEG 回退。 | skill_18_rtsp_streaming_output.md |