From f4fe214d5c647eb7d1af0063cf8b56a6cf4777d9 Mon Sep 17 00:00:00 2001 From: MegEngine Date: Tue, 14 Sep 2021 20:11:34 +0800 Subject: [PATCH 1/2] feat(project): fix memleak; add tutorial-01 --- Cargo.lock | 16 +- README.md | 14 +- doc_link_checker.py | 40 + .../01-single-classification-model.zh.md | 149 +++ .../appendix-A-graph-definition.md} | 4 +- .../appendix-B-python-plugin.zh.md} | 4 - docs/how-to-build-and-run/.gitattributes | 1 + .../build-from-source.zh.md} | 15 +- .../build-with-docker.zh.md} | 7 +- .../generate-rtsp.zh.md} | 0 .../run-in-15-minutes.zh.md | 78 ++ docs/{ => how-to-build-and-run}/workflow.png | Bin docs/how-to-pack-python-whl.zh.md | 2 +- docs/how-to-run-in-15-minutes.zh.md | 90 -- flow-derive/src/internal.rs | 9 +- flow-plugins/examples/image.toml | 3 +- flow-plugins/examples/video.toml | 2 +- flow-python/examples/cat_finder/README.md | 13 +- .../examples/electric_bicycle/README.md | 13 +- .../simple_classification/classify.py | 38 + .../examples/simple_classification/dump.py | 29 + .../simple_classification/image_cpu.toml | 32 + .../examples/simple_classification/lite.py | 78 ++ .../simple_classification/synset_words.txt | 1000 +++++++++++++++++ flow-python/setup.py | 2 +- flow-rs/src/channel/inner.rs | 28 +- flow-rs/src/channel/receiver.rs | 74 +- flow-rs/src/debug/feature.rs | 18 +- flow-rs/src/graph/debug.rs | 22 +- flow-rs/src/graph/mod.rs | 22 +- flow-rs/src/graph/subgraph.rs | 16 +- flow-rs/src/lib.rs | 4 +- flow-rs/src/loader/python/channel.rs | 26 +- flow-rs/src/node/shared.rs | 58 +- flow-rs/tests/04-isolated.rs | 38 + flow-rs/tests/nodes_ext.rs | 39 + 36 files changed, 1785 insertions(+), 199 deletions(-) create mode 100644 doc_link_checker.py create mode 100644 docs/how-to-add-my-service/01-single-classification-model.zh.md rename docs/{how-to-add-graph.zh.md => how-to-add-my-service/appendix-A-graph-definition.md} (98%) rename docs/{how-to-add-plugins.zh.md => how-to-add-my-service/appendix-B-python-plugin.zh.md} (98%) create mode 100644 docs/how-to-build-and-run/.gitattributes rename docs/{how-to-build-from-source.zh.md => how-to-build-and-run/build-from-source.zh.md} (85%) rename docs/{how-to-build-with-docker.zh.md => how-to-build-and-run/build-with-docker.zh.md} (57%) rename docs/{how-to-generate-rtsp.zh.md => how-to-build-and-run/generate-rtsp.zh.md} (100%) create mode 100644 docs/how-to-build-and-run/run-in-15-minutes.zh.md rename docs/{ => how-to-build-and-run}/workflow.png (100%) delete mode 100644 docs/how-to-run-in-15-minutes.zh.md create mode 100644 flow-python/examples/simple_classification/classify.py create mode 100644 flow-python/examples/simple_classification/dump.py create mode 100644 flow-python/examples/simple_classification/image_cpu.toml create mode 100644 flow-python/examples/simple_classification/lite.py create mode 100644 flow-python/examples/simple_classification/synset_words.txt create mode 100644 flow-rs/tests/04-isolated.rs diff --git a/Cargo.lock b/Cargo.lock index 7787025..7e073a8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1575,9 +1575,9 @@ dependencies = [ [[package]] name = "pyo3" -version = "0.14.1" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "338f7f3701e11fd7f76508c91fbcaabc982564bcaf4d1ca7e1574ff2b4778aec" +checksum = "35100f9347670a566a67aa623369293703322bb9db77d99d7df7313b575ae0c8" dependencies = [ "cfg-if 1.0.0", "indoc", @@ -1591,18 +1591,18 @@ dependencies = [ [[package]] name = "pyo3-build-config" -version = "0.14.1" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcb2e98cc9ccc83d4f7115c8f925e0057e88c8d324b1bc4c2db4a7270c06ac9d" +checksum = "d12961738cacbd7f91b7c43bc25cfeeaa2698ad07a04b3be0aa88b950865738f" dependencies = [ "once_cell", ] [[package]] name = "pyo3-macros" -version = "0.14.1" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfb8671a42d0ecc4bec8cc107ae96d49292ca20cd1968e09b98af4aafd516adf" +checksum = "fc0bc5215d704824dfddddc03f93cb572e1155c68b6761c37005e1c288808ea8" dependencies = [ "pyo3-macros-backend", "quote", @@ -1611,9 +1611,9 @@ dependencies = [ [[package]] name = "pyo3-macros-backend" -version = "0.14.1" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9addf6dc422f05d4949cc0990195ee74fa43e3c3780cc9a1972fe9e7b68a9f48" +checksum = "71623fc593224afaab918aa3afcaf86ed2f43d34f6afde7f3922608f253240df" dependencies = [ "proc-macro2", "pyo3-build-config", diff --git a/README.md b/README.md index e4b96a1..da96848 100644 --- a/README.md +++ b/README.md @@ -21,12 +21,14 @@ MegFlow 是一个面向视觉应用的流式计算框架, 目标是简单、高 - 基础测试工具,支持插件沙盒,用于单测插件 ## HowTo -* [how to build with docker](docs/how-to-build-with-docker.zh.md) -* [how to build from source](docs/how-to-build-from-source.zh.md) -* [how to pack Python .whl](docs/how-to-pack-python-whl.zh.md) -* [how to add my service](docs/how-to-add-graph.zh.md) -* [how to add plugins](docs/how-to-add-plugins.zh.md) -* [how to optimize and debug](docs/how-to-debug.zh.md) +* how to build and run + * [run with prebuilt .whl](docs/how-to-build-and-run/run-in-15-minutes.zh.md) + * [build with docker](docs/how-to-build-and-run/build-with-docker.zh.md) + * [build from source](docs/how-to-build-and-run/build-from-source.zh.md) + * [generate rtsp](docs/how-to-build-and-run/generate-rtsp.zh.md) +* how to use + * [add my first service](docs/how-to-add-my-service/01-single-classification-model.zh.md) + * [how to optimize and debug](docs/how-to-debug.zh.md) * [how to contribute](docs/how-to-contribute.zh.md) * [FAQ](docs/FAQ.zh.md) diff --git a/doc_link_checker.py b/doc_link_checker.py new file mode 100644 index 0000000..493f95c --- /dev/null +++ b/doc_link_checker.py @@ -0,0 +1,40 @@ +import os +import re + +pattern = re.compile(r'\[.*?\]\(.*?\)') +def analyze_doc(home, path): + problem_list = [] + with open(path) as f: + lines = f.readlines() + for line in lines: + if '[' in line and ']' in line and '(' in line and ')' in line: + all = pattern.findall(line) + for item in all: + start = item.find('(') + end = item.find(')') + ref = item[start+1: end] + if ref.startswith('http'): + continue + fullpath = os.path.join(home, ref) + if not os.path.exists(fullpath): + problem_list.append(ref) + # print(f' {fullpath} in {path} not exist!') + else: + continue + if len(problem_list) > 0: + print(f'{path}:') + for item in problem_list: + print(f'\t {item}') + print('\n') + +def traverse(_dir): + for home, dirs, files in os.walk(_dir): + if "./target" in home or "./.github" in home: + continue + for filename in files: + if filename.endswith('.md'): + path = os.path.join(home, filename) + analyze_doc(home, path) + +if __name__ == "__main__": + traverse(".") diff --git a/docs/how-to-add-my-service/01-single-classification-model.zh.md b/docs/how-to-add-my-service/01-single-classification-model.zh.md new file mode 100644 index 0000000..46d1dbe --- /dev/null +++ b/docs/how-to-add-my-service/01-single-classification-model.zh.md @@ -0,0 +1,149 @@ +# 把分类模型变成服务 + +尽管 MegFlow 解决的是多个(20+)模型组织成 pipeline 的问题,但凡事总要一步步来。本文介绍如何 step by step 集成 1 个分类模型,最终成为图片/视频 http 服务。 + +## 准备分类模型 + +[MegEngine models]() 有现成的 imagenet 预训模型。这里把模型 dump 成 .mge。 + +新增 [dump.py](../../flow-python/examples/simple_classification/dump.py),按 [1, 3, 224, 224] 尺寸 trace 模型,打开推理优化选项,保存为 `model.mge`。 + +```bash +$ git clone https://github.com/MegEngine/models +$ cd models +$ export PYHTONPATH=${PWD}:${PYTHONPATH} +$ cd official/vision/classification/resnet +$ python3 dump.py +$ ls -lah model.mge +... +``` +`dump.py` 正在 PR 到 MegEngine/models +```bash +$ cat dump.py +... + data = mge.Tensor(np.random.random((1, 3, 224, 224))) # 准备一个样例输入 + + @jit.trace(capture_as_const=True) + def pred_func(data): + outputs = model(data) # trace 每个 opr 的 shape + return outputs + + pred_func(data) + pred_func.dump( # 保存模型 + graph_name, + arg_names=["data"], + optimize_for_inference=True, # 打开推理优化选项 + enable_fuse_conv_bias_nonlinearity=True, # 打开 fuse conv+bias+ReLU pass 推理更快 + ) +... +``` + +## 模型单测 +开发模型的推理封装,对外提供[功能内聚](https://baike.baidu.com/item/%E9%AB%98%E5%86%85%E8%81%9A%E4%BD%8E%E8%80%A6%E5%90%88/5227009)的接口。调用方传入一张或多张图片、直接获取结果,尽量避免关心内部实现(如用何种 backbone、预处理是什么、后处理是什么)。 + +```bash +$ cat flow-python/examples/simple_classification/lite.py +... + def inference(self, mat): + # 设置输入 + inp_data =self.net.get_io_tensor("data") + inp_data.set_data_by_share(img) + + # 推理 + self.net.forward() + self.net.wait() + + # 取输出 + output_keys = self.net.get_all_output_name() + output = self.net.get_io_tensor(output_keys[0]).to_numpy() + return np.argmax(output[0]) +... +$ python3 lite.py --model model.mge --path test.jpg # 测试 +2021-09-14 11:45:02.406 | INFO | __main__::81 - 285 +``` + +`285` 是分类模型最后一层的 argmax,对应含义需要查[ imagenet 数据集分类表](../../flow-python/examples/simple_classification/synset_words.txt) ,这里是 “Egyptian cat”(下标从 0 开始)。 + +## 配置计算图 +`flow-python/examples`增加`simple_classification/image_cpu.toml` + +```bash +$ cat flow-python/examples/simple_classification/image_cpu.toml +main = "tutorial_01_image" + +[[graphs]] +name = "subgraph" +inputs = [{ name = "inp", cap = 16, ports = ["classify:inp"] }] # 一、输入输出结点 +outputs = [{ name = "out", cap = 16, ports = ["classify:out"] }] +connections = [ +] + + [[graphs.nodes]] # 二、结点参数 + name = "classify" + ty = "Classify" + path = "models/simple_classification_models/resnet18.mge" + device = "cpu" + device_id = 0 +... + [[graphs.nodes]] # 三、服务类型配置 + name = "source" + ty = "ImageServer" + port = 8084 # 端口号 8084 + response = "json" +... +``` +开发时直接从别处复制一个过来即可,图片单模型服务只需要关心 3 处 +* 计算图输入、输出结点的名字。这里是`classify` +* `classify` 结点的参数。最重要的是 `ty="Classify"`指明了类名,MegFlow 将在当前目录搜索`Classify`类。path/device/device_id 分别是模型路径/用 CPU 推理/用哪个核,属于用户自定义配置 +* 服务类型。这里想运行图片服务 `ty = "ImageServer"`,如果想运行视频解析服务改 `ty = "VideoServer"`;图片服务默认返回图片,想返回 string 需要配置 `response = "json"` + +[完整的计算图 config 定义](appendix-A-graph-definition.md) + +## 实现配置中的 node + +创建文件`classify.py`,把之前实现的模型推理调起来即可 +```bash +$ cat flow-python/examples/simple_classification/classify.py +... +@register(inputs=['inp'], outputs=['out']) +class Classify: + def __init__(self, name, args): + logger.info("loading Resnet18 Classification...") + self.name = name + + # load model and warmup + self._model = PredictorLite(path=args['path'], device=args['device'], device_id=args['device_id']) + warmup_data = np.zeros((224, 224, 3), dtype=np.uint8) + self._model.inference(warmup_data) + logger.info("Resnet18 loaded.") + + def exec(self): + envelope = self.inp.recv() + if envelope is None: + return + + data = envelope.msg['data'] + result = self._model.inference(data) + self.out.send(envelope.repack(json.dumps(str(result)))) +``` +实现只有 2 点: +* `__init__` 里加载模型,做个 warmup 防止首次推理太慢 +* 解码成 BGR 的 data 在 `envelope.msg['data']`,推理,send 返回 json string + +[更多 node 说明](appendix-B-python-plugin.zh.md) + +## 运行测试 + +运行服务 +```bash +$ cd flow-python/examples +$ cargo run --example run_with_plugins -- -c simple_classification/image_cpu.toml -p simple_classification # 源码/docker 编译方式用这条命令 +``` + +浏览器打开 8084 端口服务(例如 http://10.122.101.175:8084/docs ),选择一张图“try it out”即可。 + +## 其他 + +一、http 客户端开发 + +rweb/Swagger 提供了 http RESTful API 描述文件,例如在 http://10.122.101.175:8084/openapi.json 。`swagger_codegen` 可用描述文件生成各种语言的调用代码。更多教程见 [swagger codegen tutorial ](https://swagger.io/tools/swagger-codegen/)。 diff --git a/docs/how-to-add-graph.zh.md b/docs/how-to-add-my-service/appendix-A-graph-definition.md similarity index 98% rename from docs/how-to-add-graph.zh.md rename to docs/how-to-add-my-service/appendix-A-graph-definition.md index a0f28ee..e42f2d2 100644 --- a/docs/how-to-add-graph.zh.md +++ b/docs/how-to-add-my-service/appendix-A-graph-definition.md @@ -62,7 +62,7 @@ connections = [ 对应可视化的计算图: -![](../flow-python/examples/cat_finder/images/image.png) +![](../../flow-python/examples/cat_finder/images/image.png) ## 视频范例 @@ -137,7 +137,7 @@ connections = [ ``` 视频解析服务可视化结果和图片接近: -![](../flow-python/examples/cat_finder/images/video.png) +![](../../flow-python/examples/cat_finder/images/video.png) ## 完整定义 diff --git a/docs/how-to-add-plugins.zh.md b/docs/how-to-add-my-service/appendix-B-python-plugin.zh.md similarity index 98% rename from docs/how-to-add-plugins.zh.md rename to docs/how-to-add-my-service/appendix-B-python-plugin.zh.md index 36f63ac..3cdef0d 100644 --- a/docs/how-to-add-plugins.zh.md +++ b/docs/how-to-add-my-service/appendix-B-python-plugin.zh.md @@ -39,7 +39,3 @@ MegFlow也提供了一系列异步工具 3. `join(tasks)`, `tasks`参数是一个函数列表,`join`堵塞直到`tasks`中的函数都执行完毕 4. `create_future(callback)`, `callback`参数是一个函数, 默认值为None,`create_future`返回一个`(Future, Waker)`对象 - `Future::wait`, 堵塞直到`Waker::wake`被调用,返回`Waker::wake(result)`传入的`result`参数 - -# Rust Plugins - -Coming soon diff --git a/docs/how-to-build-and-run/.gitattributes b/docs/how-to-build-and-run/.gitattributes new file mode 100644 index 0000000..30dd606 --- /dev/null +++ b/docs/how-to-build-and-run/.gitattributes @@ -0,0 +1 @@ +workflow.png filter=lfs diff=lfs merge=lfs -text diff --git a/docs/how-to-build-from-source.zh.md b/docs/how-to-build-and-run/build-from-source.zh.md similarity index 85% rename from docs/how-to-build-from-source.zh.md rename to docs/how-to-build-and-run/build-from-source.zh.md index 4df8992..94b2bfc 100644 --- a/docs/how-to-build-from-source.zh.md +++ b/docs/how-to-build-and-run/build-from-source.zh.md @@ -4,7 +4,7 @@ ### 安装 Rust ```bash -$ sudo apt install curl +$ sudo apt install yasm git build-essential ffmpeg curl $ curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh ``` @@ -14,11 +14,6 @@ $ cargo --version cargo 1.53.0 (4369396ce 2021-04-27) ``` -如果不成功,提示`Command 'cargo' not found`,可以按照提示加载一下环境变量(重新连接或打开终端也可以): -``` -source $HOME/.cargo/env -``` - > `cargo` 是 Rust 的包管理器兼编译辅助工具。类似 Java maven/go pkg/C++ CMake 的角色,更易使用。 ### 安装 python3.x (推荐 conda) @@ -49,7 +44,7 @@ $ conda activate py38 MegFlow 需要编译 ffmpeg。考虑到 ffmpeg 依赖较多、本身又是常用工具,最简单的办法就是直接装 ffmpeg 把编译依赖装上 ```bash -$ sudo apt install yasm git build-essential ffmpeg +$ sudo apt install ffmpeg $ ffmpeg ffmpeg version 3.4.8... $ sudo apt install clang @@ -68,7 +63,7 @@ $ cd flow-python $ python3 setup.py install --user ``` -**FAQ**:`error while loading shared libraries: libpython3.8.xxx`。如果使用 conda 只需要 +**常见问题**:`error while loading shared libraries: libpython3.8.xxx`,意为 libpython.so 找不到。如果使用 conda 就在 miniconda 安装目录下面,只需要设置环境变量 ```bash $ export LD_LIBRARY_PATH=`conda info --base`/pkgs/python-3.8.11-xxx/lib:${LD_LIBRARY_PATH} @@ -88,11 +83,11 @@ $ cargo run --example run_with_plugins -- -p logical_test 接下来开始运行好玩的 Python 应用 -* [猫猫围栏运行手册](../flow-python/examples/cat_finder/README.md) +* [猫猫围栏运行手册](../../flow-python/examples/cat_finder/README.md) * 图片注册猫猫 * 部署视频围栏,注册的猫离开围栏时会发通知 * 未注册的不会提示 -* [电梯电瓶车告警](../flow-python/examples/electric_bicycle/README.md) +* [电梯电瓶车告警](../../flow-python/examples/electric_bicycle/README.md) * 电梯里看到电瓶车立即报警 * Comming Soon * OCR: 通用字符识别 diff --git a/docs/how-to-build-with-docker.zh.md b/docs/how-to-build-and-run/build-with-docker.zh.md similarity index 57% rename from docs/how-to-build-with-docker.zh.md rename to docs/how-to-build-and-run/build-with-docker.zh.md index f1b6049..098e429 100644 --- a/docs/how-to-build-with-docker.zh.md +++ b/docs/how-to-build-and-run/build-with-docker.zh.md @@ -2,13 +2,14 @@ ## Build Docker Image -MegFlow 提供了 [Dockerfile](../Dockerfile),能够“可复现地”生成运行环境、减少依赖缺失的痛苦 +MegFlow 提供了 [Dockerfile](../../Dockerfile),能够“可复现地”生成运行环境、减少依赖缺失的痛苦 ```bash $ cd MegFlow $ docker build -t megflow . ``` 稍等一段时间(取决于网络和 CPU)镜像构建完成并做了基础自测 +> 注意:**不要移动 Dockerfile 文件的位置**。受 [EAR](https://www.federalregister.gov/documents/2019/10/09/2019-22210/addition-of-certain-entities-to-the-entity-list) 约束,MegFlow 无法提供现成的 docker 镜像,需要自己 build 出来,这个过程用了相对路径。 ```bash $ docker images REPOSITORY TAG IMAGE ID CREATED SIZE @@ -23,11 +24,11 @@ $ docker run -p 18081:8081 -p 18082:8082 -i -t c65e37e1df6c /bin/bash 接下来开始运行好玩的 Python 应用 -* [猫猫围栏运行手册](../flow-python/examples/cat_finder/README.md) +* [猫猫围栏运行手册](../../flow-python/examples/cat_finder/README.md) * 图片注册猫猫 * 部署视频围栏,注册的猫离开围栏时会发通知 * 未注册的不会提示 -* [电梯电瓶车告警](../flow-python/examples/electric_bicycle/README.md) +* [电梯电瓶车告警](../../flow-python/examples/electric_bicycle/README.md) * 电梯里看到电瓶车立即报警 * Comming Soon * OCR: 通用字符识别 diff --git a/docs/how-to-generate-rtsp.zh.md b/docs/how-to-build-and-run/generate-rtsp.zh.md similarity index 100% rename from docs/how-to-generate-rtsp.zh.md rename to docs/how-to-build-and-run/generate-rtsp.zh.md diff --git a/docs/how-to-build-and-run/run-in-15-minutes.zh.md b/docs/how-to-build-and-run/run-in-15-minutes.zh.md new file mode 100644 index 0000000..12de904 --- /dev/null +++ b/docs/how-to-build-and-run/run-in-15-minutes.zh.md @@ -0,0 +1,78 @@ +# Run in 15 minutes + +## 安装 python3.x (推荐 conda) + +打开 [miniconda 官网](https://docs.conda.io/en/latest/miniconda.html) 下载 miniconda 安装包,修改权限并安装。 + +```bash +$ wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh +$ chmod a+x Miniconda3-latest-Linux-x86_64.sh +$ ./Miniconda3-latest-Linux-x86_64.sh +``` + +安装时接受 conda 修改默认 .bashrc 环境变量(zsh 用户还需自行修改 .zshrc 中的 conda initialize 配置)。成功后 `conda` 可正常运行 +```bash +$ conda --version +conda 4.10.3 +``` + +创建一个 Python3.x(这里以 3.8 为例) 的环境,激活。 +```bash +$ conda create --name py38 python=3.8 +$ conda activate py38 +``` + +## 安装 Prebuilt 包 + +```bash +$ python3 -m pip install megflow # On brain++ +$ python3 -m pip install -i https://pypi.megvii-inc.com/simple megflow # On company server +``` + +完成后应该可以 `import megflow` +```bash +$ python3 +Python 3.8.3 (default, May 19 2020, 18:47:26) +[GCC 7.3.0] :: Anaconda, Inc. on linux +Type "help", "copyright", "credits" or "license" for more information. +>>> import megflow +``` +.whl 打包了可执行文件 `run_with_plugins`,如果使用 conda 位置应该在 +```bash +$ cd ${HOME}/miniconda3/envs/py38/lib/python3.8/site-packages/megflow/ +$ sudo apt install build-essential -y +$ ldd run_with_plugins # 可看到仅依赖常见库 +$ ./run_with_plugins --help +run_with_plugins 1.0 +megvii +... +``` + +## Python“开机自检” + +```bash +$ cd ${MegFlow_PATH}/flow-python/examples # 这行必须 +$ run_with_plugins -p logical_test +``` + +`logical_test` 是 examples 下最基础的计算图测试用例,运行能正常结束表示 MegFlow 编译成功、基本语义无问题。 + +此处常见问题:`error while loading shared libraries: libpython3.8.xxx`。如果使用 conda 只需要 +```bash +$ export LD_LIBRARY_PATH=`conda info --base`/pkgs/python-3.8.11-xxx/lib:${LD_LIBRARY_PATH} +``` + +> 工作原理:[megflow](../../flow-python/megflow/__init__.py) 仅是一层接口,由 run_with_plugins “注入”建图/调度/优化等实现。 + +## Python Built-in Applications + +接下来开始运行好玩的 Python 应用 + +* [猫猫围栏运行手册](../../flow-python/examples/cat_finder/README.md) + * 图片注册猫猫 + * 部署视频围栏,注册的猫离开围栏时会发通知 + * 未注册的不会提示 +* [电梯电瓶车告警](../../flow-python/examples/electric_bicycle/README.md) + * 电梯里看到电瓶车立即报警 +* Comming Soon + * OCR: 通用字符识别 diff --git a/docs/workflow.png b/docs/how-to-build-and-run/workflow.png similarity index 100% rename from docs/workflow.png rename to docs/how-to-build-and-run/workflow.png diff --git a/docs/how-to-pack-python-whl.zh.md b/docs/how-to-pack-python-whl.zh.md index 30010e6..ce255b9 100644 --- a/docs/how-to-pack-python-whl.zh.md +++ b/docs/how-to-pack-python-whl.zh.md @@ -3,7 +3,7 @@ 打成 whl 包,使用方直接安装即可,不再需要编译。 ## 依赖 -相关 Rust/Python 环境得 Ready,见[源码编译文档](how-to-build-from-source.zh.md)。 +相关 Rust/Python 环境得 Ready,见[源码编译文档](how-to-build-and-run/build-from-source.zh.md)。 ## 执行 所有命令都已写好,复制执行即可。最终安装包都在 `dist` 目录 diff --git a/docs/how-to-run-in-15-minutes.zh.md b/docs/how-to-run-in-15-minutes.zh.md deleted file mode 100644 index 36a1273..0000000 --- a/docs/how-to-run-in-15-minutes.zh.md +++ /dev/null @@ -1,90 +0,0 @@ -# Run with Prebuilt .whl - -**因[不可抗力](http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html) MegFlow 现在不能分发完整的 prebuilt 包,推荐用 docker/源码方式** - -* [docker 方式运行(步骤更少)](how-to-build-from-source.zh.md) -* [源码编译运行]() - -**下面的文档不可用** - -## 安装 python3.x (推荐 conda) - -打开 [miniconda 官网](https://docs.conda.io/en/latest/miniconda.html) 下载 miniconda 安装包,修改权限并安装。 - -```bash -$ wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -$ chmod a+x Miniconda3-latest-Linux-x86_64.sh -$ ./Miniconda3-latest-Linux-x86_64.sh -``` - -安装时接受 conda 修改默认 .bashrc 环境变量(zsh 用户还需自行修改 .zshrc 中的 conda initialize 配置)。成功后 `conda` 可正常运行 -```bash -$ conda --version -conda 4.10.3 -``` - -创建一个 Python3.x(这里以 3.8 为例) 的环境,激活。 -```bash -$ conda create --name py38 python=3.8 -$ conda activate py38 -``` - -## 安装 Prebuilt 包 - -从 [MegFlow release](https://github.com/MegEngine/MegFlow/releases) 下载对应 python 版本的 .whl 包,安装 -```bash -$ python3 -m pip install megflow-0.1.0-py38-none-linux_x86_64.whl --force-reinstall -``` - -.whl 封装了 `megflow`和 Rust 可执行文件 `run_with_plugins_python_wrap`。完成后应该可以运行 -```bash -$ run_with_plugins_python_wrap --help -run_with_plugins 1.0 -megvii -... -$ python3 -Python 3.8.3 (default, May 19 2020, 18:47:26) -[GCC 7.3.0] :: Anaconda, Inc. on linux -Type "help", "copyright", "credits" or "license" for more information. ->>> import megflow -``` - -**FAQ**:`error while loading shared libraries: libpython3.8.xxx`。如果使用 conda 只需要 -```bash -$ export LD_LIBRARY_PATH=/home/`whoami`/miniconda3/pkgs/python-3.8.11-h12debd9_0_cpython/lib:${LD_LIBRARY_PATH} -``` - -被封装的内容安装在 miniconda3 路径下,有兴趣不妨确认一下 -```bash -$ cd ${HOME}/miniconda3/envs/py38/lib/python3.8/site-packages/megflow/ -$ sudo apt install build-essential -y -$ ldd run_with_plugins # 可看到仅依赖常见库 -$ ./run_with_plugins --help -run_with_plugins 1.0 -megvii -... -``` - -## Python“开机自检” - -`logical_test` 是 MegFlow/flow-python/examples 下最基础的计算图测试用例,运行能正常结束表示 MegFlow 编译成功、基本语义无问题。 -```bash -$ cd ${MegFlow_PATH}/flow-python/examples -$ run_with_plugins_python_wrap -p logical_test -... -``` - -> 工作原理:[megflow](../flow-python/megflow/__init__.py) 仅是一层接口,由 run_with_plugins “注入”建图/调度/优化等实现。 - -## Python Built-in Applications - -接下来开始运行好玩的 Python 应用 - -* [猫猫围栏运行手册](../flow-python/examples/cat_finder/README.md) - * 图片注册猫猫 - * 部署视频围栏,注册的猫离开围栏时会发通知 - * 未注册的不会提示 -* [电梯电瓶车告警](../flow-python/examples/electric_bicycle/README.md) - * 电梯里看到电瓶车立即报警 -* Comming Soon - * OCR: 通用字符识别 diff --git a/flow-derive/src/internal.rs b/flow-derive/src/internal.rs index 574af69..6b12ce3 100644 --- a/flow-derive/src/internal.rs +++ b/flow-derive/src/internal.rs @@ -90,10 +90,7 @@ pub fn feature_expand(mut input: FeatureDeclare) -> TokenStream { fn #init() { flow_rs::registry::__submit_only_in_ctor(stringify!(#name), feature::FeatureCommand { start: Box::new(|k, json| { - if #name.enable - .compare_exchange(false, true, std::sync::atomic::Ordering::SeqCst, std::sync::atomic::Ordering::SeqCst).is_ok() { - #name.notify(); - } + #name.notify(); let mut args = #args_name.write().unwrap(); args.insert(k, serde_json::from_value(json).unwrap()); }), @@ -122,11 +119,11 @@ pub fn feature_expand(mut input: FeatureDeclare) -> TokenStream { }).ok(); } if args.is_empty() { - #name.enable.store(false, std::sync::atomic::Ordering::Relaxed); + #name.disable(); } }), disable: Box::new(|| { - #name.enable.store(false, std::sync::atomic::Ordering::Relaxed); + #name.disable(); }), }); } diff --git a/flow-plugins/examples/image.toml b/flow-plugins/examples/image.toml index b219ae5..5dcda28 100644 --- a/flow-plugins/examples/image.toml +++ b/flow-plugins/examples/image.toml @@ -7,4 +7,5 @@ connections = [{ cap=16, ports=["server:inp", "server:out"] }] [[graphs.nodes]] name = "server" ty = "ImageServer" - port = 3030 + port = 3030 # int, the port on which server will listen + response = "json" # json | image, which means response format diff --git a/flow-plugins/examples/video.toml b/flow-plugins/examples/video.toml index c98d0e3..926cdac 100644 --- a/flow-plugins/examples/video.toml +++ b/flow-plugins/examples/video.toml @@ -10,7 +10,7 @@ connections = [ [[graphs.nodes]] name = "server" ty = "VideoServer" - port = 8080 + port = 8080 # int, the port on which server will listen [[graphs.nodes]] name = "trans" diff --git a/flow-python/examples/cat_finder/README.md b/flow-python/examples/cat_finder/README.md index eed6caa..bf6cec5 100644 --- a/flow-python/examples/cat_finder/README.md +++ b/flow-python/examples/cat_finder/README.md @@ -55,10 +55,15 @@ $ cd flow-python/examples $ pip3 install -r requires.txt $ cargo run --example run_with_plugins -- -c cat_finder/image_gpu.toml -p cat_finder # 有 GPU 的机器执行这个 $ cargo run --example run_with_plugins -- -c cat_finder/image_cpu.toml -p cat_finder # 无 GPU 的 laptop 执行这句 -$ run_with_plugins_python_wrap -c cat_finder/image_cpu.toml -p cat_finder # 如果用 prebuilt 包,不需要`cargo run --example run_with_plugins --` ``` -现在 8081 端口部署了“猫体注册”服务,只需要打开浏览器上传图片、猫咪名称即可。`cat_finder/image_gpu.toml` 详细解释见 [how-to-add-graph](../../../docs/how-to-add-graph.zh.md) 。这里只需要浏览器打开主机所在 8081 端口服务。 +**常见问题**:`error while loading shared libraries: libpython3.8.xxx`,意为 libpython.so 找不到。如果使用 conda 就在 miniconda 安装目录下面,只需要设置环境变量 + +```bash +$ export LD_LIBRARY_PATH=`conda info --base`/pkgs/python-3.8.11-xxx/lib:${LD_LIBRARY_PATH} +``` + +现在 8081 端口部署了“猫体注册”服务,只需要打开浏览器上传图片、猫咪名称即可。`cat_finder/image_gpu.toml` 详细解释见 [how-to-add-graph](../../../docs/how-to-add-my-service/appendix-A-graph-definition.md) 。这里只需要浏览器打开主机所在 8081 端口服务(如 http://127.0.0.1:8081/docs )。 ![](images/cat_finder_image_select.jpg) @@ -106,7 +111,7 @@ $ ffmpeg -re -stream_loop -1 -i ${models}/cat_finder_testdata/test1.ts -c copy - * 想用 laptop/树莓派摄像头实时流,可搜索 Camera 推流教程 * 也可以手机拍摄视频,再用 ffmpeg 转成 .ts 格式推到 live555 server -相关教程已整合在 [如何生成自己的 rtsp 流地址](../../../docs/how-to-generate-rtsp.zh.md) 。 +相关教程已整合在 [如何生成自己的 rtsp 流地址](../../../docs/how-to-build-and-run/generate-rtsp.zh.md) 。 启动视频识别服务 ```bash @@ -115,7 +120,7 @@ $ cargo run --example run_with_plugins -- -c cat_finder/video_gpu.toml -p cat_f $ cargo run --example run_with_plugins -- -c cat_finder/video_cpu.toml -p cat_finder # 无 GPU 的设备用这句 $ run_with_plugins_python_wrap -c cat_finder/video_cpu.toml -p cat_finder # 如果能用 prebuilt 包,不需要用`cargo run --example run_with_plugins --`编译 ``` -服务配置文件在`cat_finder/video_gpu.toml`,详细解释见 [how-to-add-graph](../../../docs/how-to-add-graph.zh.md) 。这里只需要打开 8082 端口服务。 +打开 8082 端口服务(如 http://127.0.0.1:8082/docs )。 ```bash $ google-chrome-stable http://127.0.0.1:8082/docs diff --git a/flow-python/examples/electric_bicycle/README.md b/flow-python/examples/electric_bicycle/README.md index dceedf6..c26c780 100644 --- a/flow-python/examples/electric_bicycle/README.md +++ b/flow-python/examples/electric_bicycle/README.md @@ -24,7 +24,7 @@ $ ln -s ${DOWNLOAD_DIR}/models models 安装运行依赖 ```bash $ sudo apt install redis-server -$ redis-server +$ redis-server & ... $ conda activate py38 $ pip3 install onnxruntime --user @@ -32,16 +32,17 @@ $ pip3 install onnxruntime --user 准备一个 rtsp 视频流地址,做测试输入。 -* laptop 或树莓派可搜索 Camera 推流教程。见 [如何生成自己的 rtsp 流地址](../../../docs/how-to-generate-rtsp.zh.md) -* 也可以手机拍摄视频,再用 ffmpeg 转成 .ts 格式放到 live555 server。见 [如何生成自己的 rtsp 流地址](../../../docs/how-to-generate-rtsp.zh.md) +* laptop 或树莓派可搜索 Camera 推流教程 +* 也可以手机拍摄视频,再用 ffmpeg 转成 .ts 格式放到 live555 server + +相关教程已整合在 [如何生成自己的 rtsp 流地址](../../../docs/how-to-build-and-run/generate-rtsp.zh.md) 。 启动服务 ```bash $ cd flow-python/examples -$ run_with_plugins_python_wrap -c electric_bicycle/electric_bicycle.toml -p electric_bicycle # prebuilt 安装用这个,不需要`cargo run`来编译 -$ cargo run --example run_with_plugins -- -c electric_bicycle/electric_bicycle.toml -p electric_bicycle # 源码或 docker 安装用这个 +$ cargo run --example run_with_plugins -- -c electric_bicycle/electric_bicycle.toml -p electric_bicycle ``` -服务配置文件在`electric_bicycle/electric_bicycle.toml`,解释参考 [how-to-add-graph](../../../docs/how-to-add-graph.zh.md) 。这里只需要打开 8083 端口服务,操作和[猫猫围栏](../cat_finder/README.md) 近似。 +服务配置文件在`electric_bicycle/electric_bicycle.toml`,解释参考 [how-to-add-graph](../../../docs/how-to-add-my-service/01-single-classification-model.zh.md) 。这里只需要打开 8083 端口服务,操作和[猫猫围栏](../cat_finder/README.md) 近似。 ```bash $ google-chrome-stable http://127.0.0.1:8083/docs diff --git a/flow-python/examples/simple_classification/classify.py b/flow-python/examples/simple_classification/classify.py new file mode 100644 index 0000000..8b61c8b --- /dev/null +++ b/flow-python/examples/simple_classification/classify.py @@ -0,0 +1,38 @@ +# MegFlow is Licensed under the Apache License, Version 2.0 (the "License") +# +# Copyright (c) 2019-2021 Megvii Inc. All rights reserved. +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT ARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +#!/usr/bin/env python +# coding=utf-8 + +import numpy as np +from loguru import logger +from megflow import register +import json + +from .lite import * + +@register(inputs=['inp'], outputs=['out']) +class Classify: + def __init__(self, name, args): + logger.info("loading Resnet18 Classification...") + self.name = name + + # load ReID model and warmup + self._model = PredictorLite(path=args['path'], device=args['device'], device_id=args['device_id']) + warmup_data = np.zeros((224, 224, 3), dtype=np.uint8) + self._model.inference(warmup_data) + logger.info("Resnet18 loaded.") + + def exec(self): + envelope = self.inp.recv() + if envelope is None: + return + + data = envelope.msg['data'] + result = self._model.inference(data) + self.out.send(envelope.repack(json.dumps(str(result)))) diff --git a/flow-python/examples/simple_classification/dump.py b/flow-python/examples/simple_classification/dump.py new file mode 100644 index 0000000..16ec1a8 --- /dev/null +++ b/flow-python/examples/simple_classification/dump.py @@ -0,0 +1,29 @@ +import model as resnet_model +import megengine as mge +from megengine import jit +import numpy as np + +def dump_static_graph(model, graph_name="model.mge"): + model.eval() + + data = mge.Tensor(np.random.random((1, 3, 224, 224))) + + @jit.trace(capture_as_const=True) + def pred_func(data): + outputs = model(data) + return outputs + + pred_func(data) + pred_func.dump( + graph_name, + arg_names=["data"], + optimize_for_inference=True, + enable_fuse_conv_bias_nonlinearity=True, + ) + +def main(): + model = resnet_model.__dict__['resnet18'](pretrained=True) + dump_static_graph(model) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/flow-python/examples/simple_classification/image_cpu.toml b/flow-python/examples/simple_classification/image_cpu.toml new file mode 100644 index 0000000..e2977f8 --- /dev/null +++ b/flow-python/examples/simple_classification/image_cpu.toml @@ -0,0 +1,32 @@ +main = "tutorial_01_image" + +[[graphs]] +name = "subgraph" +inputs = [{ name = "inp", cap = 16, ports = ["classify:inp"] }] +outputs = [{ name = "out", cap = 16, ports = ["classify:out"] }] +connections = [ +] + + [[graphs.nodes]] + name = "classify" + ty = "Classify" + path = "models/simple_classification_models/resnet18.mge" + device = "cpu" + device_id = 0 + +[[graphs]] +name = "tutorial_01_image" +connections = [ + { cap = 16, ports = ["source:out", "destination:inp"] }, + { cap = 16, ports = ["source:inp", "destination:out"] } +] + + [[graphs.nodes]] + name = "source" + ty = "ImageServer" + port = 8084 + response = "json" + + [[graphs.nodes]] + name = "destination" + ty = "subgraph" diff --git a/flow-python/examples/simple_classification/lite.py b/flow-python/examples/simple_classification/lite.py new file mode 100644 index 0000000..8983701 --- /dev/null +++ b/flow-python/examples/simple_classification/lite.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python3 +# -*- coding:utf-8 -*- +# Copyright (c) Megvii, Inc. and its affiliates. + +import cv2 +import megenginelite as mgelite +from loguru import logger +import numpy as np + +class PredictorLite(object): + def __init__( + self, + path, + device="gpu", + device_id=0, + ): + + if "gpu" in device.lower(): + device_type=mgelite.LiteDeviceType.LITE_CUDA + else: + device_type=mgelite.LiteDeviceType.LITE_CPU + + net_config = mgelite.LiteConfig(device_type=device_type) + ios = mgelite.LiteNetworkIO() + ios.add_input(mgelite.LiteIO("data", is_host=True)) + + net = mgelite.LiteNetwork(config=net_config, io=ios) + net.device_id = device_id + net.load(path) + + self.net = net + + def preprocess(self, image, input_size, scale_im, mean, std, swap=(2, 0, 1)): + if image is None: + logger.error("image is None") + return image + image = cv2.resize(image, input_size) + image = image.astype(np.float32) + image = image[:, :, ::-1] + if scale_im: + image /= 255.0 + if mean is not None: + image -= mean + if std is not None: + image /= std + image = image.transpose(swap) + image = np.ascontiguousarray(image, dtype=np.float32) + return image + + def inference(self, mat): + img = self.preprocess(mat, input_size=(224,224), scale_im = False, mean=[103.530, 116.280, 123.675], std=[57.375, 57.120, 58.395]) + + # build input tensor + inp_data =self.net.get_io_tensor("data") + inp_data.set_data_by_share(img) + + # forward + self.net.forward() + self.net.wait() + + # postprocess + output_keys = self.net.get_all_output_name() + output = self.net.get_io_tensor(output_keys[0]).to_numpy() + return np.argmax(output[0]) + +def make_parser(): + import argparse + parser = argparse.ArgumentParser("Classification Demo!") + parser.add_argument("--path", default="./test.png", help="path to images or video") + parser.add_argument("--model", default=None, type=str, help=".mge for eval") + return parser + +if __name__ == "__main__": + args = make_parser().parse_args() + predictor = PredictorLite(args.model) + img = cv2.imread(args.path) + output = predictor.inference(img) + logger.info(f'{output}') diff --git a/flow-python/examples/simple_classification/synset_words.txt b/flow-python/examples/simple_classification/synset_words.txt new file mode 100644 index 0000000..a9e8c7f --- /dev/null +++ b/flow-python/examples/simple_classification/synset_words.txt @@ -0,0 +1,1000 @@ +n01440764 tench, Tinca tinca +n01443537 goldfish, Carassius auratus +n01484850 great white shark, white shark, man-eater, man-eating shark, Carcharodon carcharias +n01491361 tiger shark, Galeocerdo cuvieri +n01494475 hammerhead, hammerhead shark +n01496331 electric ray, crampfish, numbfish, torpedo +n01498041 stingray +n01514668 cock +n01514859 hen +n01518878 ostrich, Struthio camelus +n01530575 brambling, Fringilla montifringilla +n01531178 goldfinch, Carduelis carduelis +n01532829 house finch, linnet, Carpodacus mexicanus +n01534433 junco, snowbird +n01537544 indigo bunting, indigo finch, indigo bird, Passerina cyanea +n01558993 robin, American robin, Turdus migratorius +n01560419 bulbul +n01580077 jay +n01582220 magpie +n01592084 chickadee +n01601694 water ouzel, dipper +n01608432 kite +n01614925 bald eagle, American eagle, Haliaeetus leucocephalus +n01616318 vulture +n01622779 great grey owl, great gray owl, Strix nebulosa +n01629819 European fire salamander, Salamandra salamandra +n01630670 common newt, Triturus vulgaris +n01631663 eft +n01632458 spotted salamander, Ambystoma maculatum +n01632777 axolotl, mud puppy, Ambystoma mexicanum +n01641577 bullfrog, Rana catesbeiana +n01644373 tree frog, tree-frog +n01644900 tailed frog, bell toad, ribbed toad, tailed toad, Ascaphus trui +n01664065 loggerhead, loggerhead turtle, Caretta caretta +n01665541 leatherback turtle, leatherback, leathery turtle, Dermochelys coriacea +n01667114 mud turtle +n01667778 terrapin +n01669191 box turtle, box tortoise +n01675722 banded gecko +n01677366 common iguana, iguana, Iguana iguana +n01682714 American chameleon, anole, Anolis carolinensis +n01685808 whiptail, whiptail lizard +n01687978 agama +n01688243 frilled lizard, Chlamydosaurus kingi +n01689811 alligator lizard +n01692333 Gila monster, Heloderma suspectum +n01693334 green lizard, Lacerta viridis +n01694178 African chameleon, Chamaeleo chamaeleon +n01695060 Komodo dragon, Komodo lizard, dragon lizard, giant lizard, Varanus komodoensis +n01697457 African crocodile, Nile crocodile, Crocodylus niloticus +n01698640 American alligator, Alligator mississipiensis +n01704323 triceratops +n01728572 thunder snake, worm snake, Carphophis amoenus +n01728920 ringneck snake, ring-necked snake, ring snake +n01729322 hognose snake, puff adder, sand viper +n01729977 green snake, grass snake +n01734418 king snake, kingsnake +n01735189 garter snake, grass snake +n01737021 water snake +n01739381 vine snake +n01740131 night snake, Hypsiglena torquata +n01742172 boa constrictor, Constrictor constrictor +n01744401 rock python, rock snake, Python sebae +n01748264 Indian cobra, Naja naja +n01749939 green mamba +n01751748 sea snake +n01753488 horned viper, cerastes, sand viper, horned asp, Cerastes cornutus +n01755581 diamondback, diamondback rattlesnake, Crotalus adamanteus +n01756291 sidewinder, horned rattlesnake, Crotalus cerastes +n01768244 trilobite +n01770081 harvestman, daddy longlegs, Phalangium opilio +n01770393 scorpion +n01773157 black and gold garden spider, Argiope aurantia +n01773549 barn spider, Araneus cavaticus +n01773797 garden spider, Aranea diademata +n01774384 black widow, Latrodectus mactans +n01774750 tarantula +n01775062 wolf spider, hunting spider +n01776313 tick +n01784675 centipede +n01795545 black grouse +n01796340 ptarmigan +n01797886 ruffed grouse, partridge, Bonasa umbellus +n01798484 prairie chicken, prairie grouse, prairie fowl +n01806143 peacock +n01806567 quail +n01807496 partridge +n01817953 African grey, African gray, Psittacus erithacus +n01818515 macaw +n01819313 sulphur-crested cockatoo, Kakatoe galerita, Cacatua galerita +n01820546 lorikeet +n01824575 coucal +n01828970 bee eater +n01829413 hornbill +n01833805 hummingbird +n01843065 jacamar +n01843383 toucan +n01847000 drake +n01855032 red-breasted merganser, Mergus serrator +n01855672 goose +n01860187 black swan, Cygnus atratus +n01871265 tusker +n01872401 echidna, spiny anteater, anteater +n01873310 platypus, duckbill, duckbilled platypus, duck-billed platypus, Ornithorhynchus anatinus +n01877812 wallaby, brush kangaroo +n01882714 koala, koala bear, kangaroo bear, native bear, Phascolarctos cinereus +n01883070 wombat +n01910747 jellyfish +n01914609 sea anemone, anemone +n01917289 brain coral +n01924916 flatworm, platyhelminth +n01930112 nematode, nematode worm, roundworm +n01943899 conch +n01944390 snail +n01945685 slug +n01950731 sea slug, nudibranch +n01955084 chiton, coat-of-mail shell, sea cradle, polyplacophore +n01968897 chambered nautilus, pearly nautilus, nautilus +n01978287 Dungeness crab, Cancer magister +n01978455 rock crab, Cancer irroratus +n01980166 fiddler crab +n01981276 king crab, Alaska crab, Alaskan king crab, Alaska king crab, Paralithodes camtschatica +n01983481 American lobster, Northern lobster, Maine lobster, Homarus americanus +n01984695 spiny lobster, langouste, rock lobster, crawfish, crayfish, sea crawfish +n01985128 crayfish, crawfish, crawdad, crawdaddy +n01986214 hermit crab +n01990800 isopod +n02002556 white stork, Ciconia ciconia +n02002724 black stork, Ciconia nigra +n02006656 spoonbill +n02007558 flamingo +n02009229 little blue heron, Egretta caerulea +n02009912 American egret, great white heron, Egretta albus +n02011460 bittern +n02012849 crane +n02013706 limpkin, Aramus pictus +n02017213 European gallinule, Porphyrio porphyrio +n02018207 American coot, marsh hen, mud hen, water hen, Fulica americana +n02018795 bustard +n02025239 ruddy turnstone, Arenaria interpres +n02027492 red-backed sandpiper, dunlin, Erolia alpina +n02028035 redshank, Tringa totanus +n02033041 dowitcher +n02037110 oystercatcher, oyster catcher +n02051845 pelican +n02056570 king penguin, Aptenodytes patagonica +n02058221 albatross, mollymawk +n02066245 grey whale, gray whale, devilfish, Eschrichtius gibbosus, Eschrichtius robustus +n02071294 killer whale, killer, orca, grampus, sea wolf, Orcinus orca +n02074367 dugong, Dugong dugon +n02077923 sea lion +n02085620 Chihuahua +n02085782 Japanese spaniel +n02085936 Maltese dog, Maltese terrier, Maltese +n02086079 Pekinese, Pekingese, Peke +n02086240 Shih-Tzu +n02086646 Blenheim spaniel +n02086910 papillon +n02087046 toy terrier +n02087394 Rhodesian ridgeback +n02088094 Afghan hound, Afghan +n02088238 basset, basset hound +n02088364 beagle +n02088466 bloodhound, sleuthhound +n02088632 bluetick +n02089078 black-and-tan coonhound +n02089867 Walker hound, Walker foxhound +n02089973 English foxhound +n02090379 redbone +n02090622 borzoi, Russian wolfhound +n02090721 Irish wolfhound +n02091032 Italian greyhound +n02091134 whippet +n02091244 Ibizan hound, Ibizan Podenco +n02091467 Norwegian elkhound, elkhound +n02091635 otterhound, otter hound +n02091831 Saluki, gazelle hound +n02092002 Scottish deerhound, deerhound +n02092339 Weimaraner +n02093256 Staffordshire bullterrier, Staffordshire bull terrier +n02093428 American Staffordshire terrier, Staffordshire terrier, American pit bull terrier, pit bull terrier +n02093647 Bedlington terrier +n02093754 Border terrier +n02093859 Kerry blue terrier +n02093991 Irish terrier +n02094114 Norfolk terrier +n02094258 Norwich terrier +n02094433 Yorkshire terrier +n02095314 wire-haired fox terrier +n02095570 Lakeland terrier +n02095889 Sealyham terrier, Sealyham +n02096051 Airedale, Airedale terrier +n02096177 cairn, cairn terrier +n02096294 Australian terrier +n02096437 Dandie Dinmont, Dandie Dinmont terrier +n02096585 Boston bull, Boston terrier +n02097047 miniature schnauzer +n02097130 giant schnauzer +n02097209 standard schnauzer +n02097298 Scotch terrier, Scottish terrier, Scottie +n02097474 Tibetan terrier, chrysanthemum dog +n02097658 silky terrier, Sydney silky +n02098105 soft-coated wheaten terrier +n02098286 West Highland white terrier +n02098413 Lhasa, Lhasa apso +n02099267 flat-coated retriever +n02099429 curly-coated retriever +n02099601 golden retriever +n02099712 Labrador retriever +n02099849 Chesapeake Bay retriever +n02100236 German short-haired pointer +n02100583 vizsla, Hungarian pointer +n02100735 English setter +n02100877 Irish setter, red setter +n02101006 Gordon setter +n02101388 Brittany spaniel +n02101556 clumber, clumber spaniel +n02102040 English springer, English springer spaniel +n02102177 Welsh springer spaniel +n02102318 cocker spaniel, English cocker spaniel, cocker +n02102480 Sussex spaniel +n02102973 Irish water spaniel +n02104029 kuvasz +n02104365 schipperke +n02105056 groenendael +n02105162 malinois +n02105251 briard +n02105412 kelpie +n02105505 komondor +n02105641 Old English sheepdog, bobtail +n02105855 Shetland sheepdog, Shetland sheep dog, Shetland +n02106030 collie +n02106166 Border collie +n02106382 Bouvier des Flandres, Bouviers des Flandres +n02106550 Rottweiler +n02106662 German shepherd, German shepherd dog, German police dog, alsatian +n02107142 Doberman, Doberman pinscher +n02107312 miniature pinscher +n02107574 Greater Swiss Mountain dog +n02107683 Bernese mountain dog +n02107908 Appenzeller +n02108000 EntleBucher +n02108089 boxer +n02108422 bull mastiff +n02108551 Tibetan mastiff +n02108915 French bulldog +n02109047 Great Dane +n02109525 Saint Bernard, St Bernard +n02109961 Eskimo dog, husky +n02110063 malamute, malemute, Alaskan malamute +n02110185 Siberian husky +n02110341 dalmatian, coach dog, carriage dog +n02110627 affenpinscher, monkey pinscher, monkey dog +n02110806 basenji +n02110958 pug, pug-dog +n02111129 Leonberg +n02111277 Newfoundland, Newfoundland dog +n02111500 Great Pyrenees +n02111889 Samoyed, Samoyede +n02112018 Pomeranian +n02112137 chow, chow chow +n02112350 keeshond +n02112706 Brabancon griffon +n02113023 Pembroke, Pembroke Welsh corgi +n02113186 Cardigan, Cardigan Welsh corgi +n02113624 toy poodle +n02113712 miniature poodle +n02113799 standard poodle +n02113978 Mexican hairless +n02114367 timber wolf, grey wolf, gray wolf, Canis lupus +n02114548 white wolf, Arctic wolf, Canis lupus tundrarum +n02114712 red wolf, maned wolf, Canis rufus, Canis niger +n02114855 coyote, prairie wolf, brush wolf, Canis latrans +n02115641 dingo, warrigal, warragal, Canis dingo +n02115913 dhole, Cuon alpinus +n02116738 African hunting dog, hyena dog, Cape hunting dog, Lycaon pictus +n02117135 hyena, hyaena +n02119022 red fox, Vulpes vulpes +n02119789 kit fox, Vulpes macrotis +n02120079 Arctic fox, white fox, Alopex lagopus +n02120505 grey fox, gray fox, Urocyon cinereoargenteus +n02123045 tabby, tabby cat +n02123159 tiger cat +n02123394 Persian cat +n02123597 Siamese cat, Siamese +n02124075 Egyptian cat +n02125311 cougar, puma, catamount, mountain lion, painter, panther, Felis concolor +n02127052 lynx, catamount +n02128385 leopard, Panthera pardus +n02128757 snow leopard, ounce, Panthera uncia +n02128925 jaguar, panther, Panthera onca, Felis onca +n02129165 lion, king of beasts, Panthera leo +n02129604 tiger, Panthera tigris +n02130308 cheetah, chetah, Acinonyx jubatus +n02132136 brown bear, bruin, Ursus arctos +n02133161 American black bear, black bear, Ursus americanus, Euarctos americanus +n02134084 ice bear, polar bear, Ursus Maritimus, Thalarctos maritimus +n02134418 sloth bear, Melursus ursinus, Ursus ursinus +n02137549 mongoose +n02138441 meerkat, mierkat +n02165105 tiger beetle +n02165456 ladybug, ladybeetle, lady beetle, ladybird, ladybird beetle +n02167151 ground beetle, carabid beetle +n02168699 long-horned beetle, longicorn, longicorn beetle +n02169497 leaf beetle, chrysomelid +n02172182 dung beetle +n02174001 rhinoceros beetle +n02177972 weevil +n02190166 fly +n02206856 bee +n02219486 ant, emmet, pismire +n02226429 grasshopper, hopper +n02229544 cricket +n02231487 walking stick, walkingstick, stick insect +n02233338 cockroach, roach +n02236044 mantis, mantid +n02256656 cicada, cicala +n02259212 leafhopper +n02264363 lacewing, lacewing fly +n02268443 dragonfly, darning needle, devil's darning needle, sewing needle, snake feeder, snake doctor, mosquito hawk, skeeter hawk +n02268853 damselfly +n02276258 admiral +n02277742 ringlet, ringlet butterfly +n02279972 monarch, monarch butterfly, milkweed butterfly, Danaus plexippus +n02280649 cabbage butterfly +n02281406 sulphur butterfly, sulfur butterfly +n02281787 lycaenid, lycaenid butterfly +n02317335 starfish, sea star +n02319095 sea urchin +n02321529 sea cucumber, holothurian +n02325366 wood rabbit, cottontail, cottontail rabbit +n02326432 hare +n02328150 Angora, Angora rabbit +n02342885 hamster +n02346627 porcupine, hedgehog +n02356798 fox squirrel, eastern fox squirrel, Sciurus niger +n02361337 marmot +n02363005 beaver +n02364673 guinea pig, Cavia cobaya +n02389026 sorrel +n02391049 zebra +n02395406 hog, pig, grunter, squealer, Sus scrofa +n02396427 wild boar, boar, Sus scrofa +n02397096 warthog +n02398521 hippopotamus, hippo, river horse, Hippopotamus amphibius +n02403003 ox +n02408429 water buffalo, water ox, Asiatic buffalo, Bubalus bubalis +n02410509 bison +n02412080 ram, tup +n02415577 bighorn, bighorn sheep, cimarron, Rocky Mountain bighorn, Rocky Mountain sheep, Ovis canadensis +n02417914 ibex, Capra ibex +n02422106 hartebeest +n02422699 impala, Aepyceros melampus +n02423022 gazelle +n02437312 Arabian camel, dromedary, Camelus dromedarius +n02437616 llama +n02441942 weasel +n02442845 mink +n02443114 polecat, fitch, foulmart, foumart, Mustela putorius +n02443484 black-footed ferret, ferret, Mustela nigripes +n02444819 otter +n02445715 skunk, polecat, wood pussy +n02447366 badger +n02454379 armadillo +n02457408 three-toed sloth, ai, Bradypus tridactylus +n02480495 orangutan, orang, orangutang, Pongo pygmaeus +n02480855 gorilla, Gorilla gorilla +n02481823 chimpanzee, chimp, Pan troglodytes +n02483362 gibbon, Hylobates lar +n02483708 siamang, Hylobates syndactylus, Symphalangus syndactylus +n02484975 guenon, guenon monkey +n02486261 patas, hussar monkey, Erythrocebus patas +n02486410 baboon +n02487347 macaque +n02488291 langur +n02488702 colobus, colobus monkey +n02489166 proboscis monkey, Nasalis larvatus +n02490219 marmoset +n02492035 capuchin, ringtail, Cebus capucinus +n02492660 howler monkey, howler +n02493509 titi, titi monkey +n02493793 spider monkey, Ateles geoffroyi +n02494079 squirrel monkey, Saimiri sciureus +n02497673 Madagascar cat, ring-tailed lemur, Lemur catta +n02500267 indri, indris, Indri indri, Indri brevicaudatus +n02504013 Indian elephant, Elephas maximus +n02504458 African elephant, Loxodonta africana +n02509815 lesser panda, red panda, panda, bear cat, cat bear, Ailurus fulgens +n02510455 giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca +n02514041 barracouta, snoek +n02526121 eel +n02536864 coho, cohoe, coho salmon, blue jack, silver salmon, Oncorhynchus kisutch +n02606052 rock beauty, Holocanthus tricolor +n02607072 anemone fish +n02640242 sturgeon +n02641379 gar, garfish, garpike, billfish, Lepisosteus osseus +n02643566 lionfish +n02655020 puffer, pufferfish, blowfish, globefish +n02666196 abacus +n02667093 abaya +n02669723 academic gown, academic robe, judge's robe +n02672831 accordion, piano accordion, squeeze box +n02676566 acoustic guitar +n02687172 aircraft carrier, carrier, flattop, attack aircraft carrier +n02690373 airliner +n02692877 airship, dirigible +n02699494 altar +n02701002 ambulance +n02704792 amphibian, amphibious vehicle +n02708093 analog clock +n02727426 apiary, bee house +n02730930 apron +n02747177 ashcan, trash can, garbage can, wastebin, ash bin, ash-bin, ashbin, dustbin, trash barrel, trash bin +n02749479 assault rifle, assault gun +n02769748 backpack, back pack, knapsack, packsack, rucksack, haversack +n02776631 bakery, bakeshop, bakehouse +n02777292 balance beam, beam +n02782093 balloon +n02783161 ballpoint, ballpoint pen, ballpen, Biro +n02786058 Band Aid +n02787622 banjo +n02788148 bannister, banister, balustrade, balusters, handrail +n02790996 barbell +n02791124 barber chair +n02791270 barbershop +n02793495 barn +n02794156 barometer +n02795169 barrel, cask +n02797295 barrow, garden cart, lawn cart, wheelbarrow +n02799071 baseball +n02802426 basketball +n02804414 bassinet +n02804610 bassoon +n02807133 bathing cap, swimming cap +n02808304 bath towel +n02808440 bathtub, bathing tub, bath, tub +n02814533 beach wagon, station wagon, wagon, estate car, beach waggon, station waggon, waggon +n02814860 beacon, lighthouse, beacon light, pharos +n02815834 beaker +n02817516 bearskin, busby, shako +n02823428 beer bottle +n02823750 beer glass +n02825657 bell cote, bell cot +n02834397 bib +n02835271 bicycle-built-for-two, tandem bicycle, tandem +n02837789 bikini, two-piece +n02840245 binder, ring-binder +n02841315 binoculars, field glasses, opera glasses +n02843684 birdhouse +n02859443 boathouse +n02860847 bobsled, bobsleigh, bob +n02865351 bolo tie, bolo, bola tie, bola +n02869837 bonnet, poke bonnet +n02870880 bookcase +n02871525 bookshop, bookstore, bookstall +n02877765 bottlecap +n02879718 bow +n02883205 bow tie, bow-tie, bowtie +n02892201 brass, memorial tablet, plaque +n02892767 brassiere, bra, bandeau +n02894605 breakwater, groin, groyne, mole, bulwark, seawall, jetty +n02895154 breastplate, aegis, egis +n02906734 broom +n02909870 bucket, pail +n02910353 buckle +n02916936 bulletproof vest +n02917067 bullet train, bullet +n02927161 butcher shop, meat market +n02930766 cab, hack, taxi, taxicab +n02939185 caldron, cauldron +n02948072 candle, taper, wax light +n02950826 cannon +n02951358 canoe +n02951585 can opener, tin opener +n02963159 cardigan +n02965783 car mirror +n02966193 carousel, carrousel, merry-go-round, roundabout, whirligig +n02966687 carpenter's kit, tool kit +n02971356 carton +n02974003 car wheel +n02977058 cash machine, cash dispenser, automated teller machine, automatic teller machine, automated teller, automatic teller, ATM +n02978881 cassette +n02979186 cassette player +n02980441 castle +n02981792 catamaran +n02988304 CD player +n02992211 cello, violoncello +n02992529 cellular telephone, cellular phone, cellphone, cell, mobile phone +n02999410 chain +n03000134 chainlink fence +n03000247 chain mail, ring mail, mail, chain armor, chain armour, ring armor, ring armour +n03000684 chain saw, chainsaw +n03014705 chest +n03016953 chiffonier, commode +n03017168 chime, bell, gong +n03018349 china cabinet, china closet +n03026506 Christmas stocking +n03028079 church, church building +n03032252 cinema, movie theater, movie theatre, movie house, picture palace +n03041632 cleaver, meat cleaver, chopper +n03042490 cliff dwelling +n03045698 cloak +n03047690 clog, geta, patten, sabot +n03062245 cocktail shaker +n03063599 coffee mug +n03063689 coffeepot +n03065424 coil, spiral, volute, whorl, helix +n03075370 combination lock +n03085013 computer keyboard, keypad +n03089624 confectionery, confectionary, candy store +n03095699 container ship, containership, container vessel +n03100240 convertible +n03109150 corkscrew, bottle screw +n03110669 cornet, horn, trumpet, trump +n03124043 cowboy boot +n03124170 cowboy hat, ten-gallon hat +n03125729 cradle +n03126707 crane +n03127747 crash helmet +n03127925 crate +n03131574 crib, cot +n03133878 Crock Pot +n03134739 croquet ball +n03141823 crutch +n03146219 cuirass +n03160309 dam, dike, dyke +n03179701 desk +n03180011 desktop computer +n03187595 dial telephone, dial phone +n03188531 diaper, nappy, napkin +n03196217 digital clock +n03197337 digital watch +n03201208 dining table, board +n03207743 dishrag, dishcloth +n03207941 dishwasher, dish washer, dishwashing machine +n03208938 disk brake, disc brake +n03216828 dock, dockage, docking facility +n03218198 dogsled, dog sled, dog sleigh +n03220513 dome +n03223299 doormat, welcome mat +n03240683 drilling platform, offshore rig +n03249569 drum, membranophone, tympan +n03250847 drumstick +n03255030 dumbbell +n03259280 Dutch oven +n03271574 electric fan, blower +n03272010 electric guitar +n03272562 electric locomotive +n03290653 entertainment center +n03291819 envelope +n03297495 espresso maker +n03314780 face powder +n03325584 feather boa, boa +n03337140 file, file cabinet, filing cabinet +n03344393 fireboat +n03345487 fire engine, fire truck +n03347037 fire screen, fireguard +n03355925 flagpole, flagstaff +n03372029 flute, transverse flute +n03376595 folding chair +n03379051 football helmet +n03384352 forklift +n03388043 fountain +n03388183 fountain pen +n03388549 four-poster +n03393912 freight car +n03394916 French horn, horn +n03400231 frying pan, frypan, skillet +n03404251 fur coat +n03417042 garbage truck, dustcart +n03424325 gasmask, respirator, gas helmet +n03425413 gas pump, gasoline pump, petrol pump, island dispenser +n03443371 goblet +n03444034 go-kart +n03445777 golf ball +n03445924 golfcart, golf cart +n03447447 gondola +n03447721 gong, tam-tam +n03450230 gown +n03452741 grand piano, grand +n03457902 greenhouse, nursery, glasshouse +n03459775 grille, radiator grille +n03461385 grocery store, grocery, food market, market +n03467068 guillotine +n03476684 hair slide +n03476991 hair spray +n03478589 half track +n03481172 hammer +n03482405 hamper +n03483316 hand blower, blow dryer, blow drier, hair dryer, hair drier +n03485407 hand-held computer, hand-held microcomputer +n03485794 handkerchief, hankie, hanky, hankey +n03492542 hard disc, hard disk, fixed disk +n03494278 harmonica, mouth organ, harp, mouth harp +n03495258 harp +n03496892 harvester, reaper +n03498962 hatchet +n03527444 holster +n03529860 home theater, home theatre +n03530642 honeycomb +n03532672 hook, claw +n03534580 hoopskirt, crinoline +n03535780 horizontal bar, high bar +n03538406 horse cart, horse-cart +n03544143 hourglass +n03584254 iPod +n03584829 iron, smoothing iron +n03590841 jack-o'-lantern +n03594734 jean, blue jean, denim +n03594945 jeep, landrover +n03595614 jersey, T-shirt, tee shirt +n03598930 jigsaw puzzle +n03599486 jinrikisha, ricksha, rickshaw +n03602883 joystick +n03617480 kimono +n03623198 knee pad +n03627232 knot +n03630383 lab coat, laboratory coat +n03633091 ladle +n03637318 lampshade, lamp shade +n03642806 laptop, laptop computer +n03649909 lawn mower, mower +n03657121 lens cap, lens cover +n03658185 letter opener, paper knife, paperknife +n03661043 library +n03662601 lifeboat +n03666591 lighter, light, igniter, ignitor +n03670208 limousine, limo +n03673027 liner, ocean liner +n03676483 lipstick, lip rouge +n03680355 Loafer +n03690938 lotion +n03691459 loudspeaker, speaker, speaker unit, loudspeaker system, speaker system +n03692522 loupe, jeweler's loupe +n03697007 lumbermill, sawmill +n03706229 magnetic compass +n03709823 mailbag, postbag +n03710193 mailbox, letter box +n03710637 maillot +n03710721 maillot, tank suit +n03717622 manhole cover +n03720891 maraca +n03721384 marimba, xylophone +n03724870 mask +n03729826 matchstick +n03733131 maypole +n03733281 maze, labyrinth +n03733805 measuring cup +n03742115 medicine chest, medicine cabinet +n03743016 megalith, megalithic structure +n03759954 microphone, mike +n03761084 microwave, microwave oven +n03763968 military uniform +n03764736 milk can +n03769881 minibus +n03770439 miniskirt, mini +n03770679 minivan +n03773504 missile +n03775071 mitten +n03775546 mixing bowl +n03776460 mobile home, manufactured home +n03777568 Model T +n03777754 modem +n03781244 monastery +n03782006 monitor +n03785016 moped +n03786901 mortar +n03787032 mortarboard +n03788195 mosque +n03788365 mosquito net +n03791053 motor scooter, scooter +n03792782 mountain bike, all-terrain bike, off-roader +n03792972 mountain tent +n03793489 mouse, computer mouse +n03794056 mousetrap +n03796401 moving van +n03803284 muzzle +n03804744 nail +n03814639 neck brace +n03814906 necklace +n03825788 nipple +n03832673 notebook, notebook computer +n03837869 obelisk +n03838899 oboe, hautboy, hautbois +n03840681 ocarina, sweet potato +n03841143 odometer, hodometer, mileometer, milometer +n03843555 oil filter +n03854065 organ, pipe organ +n03857828 oscilloscope, scope, cathode-ray oscilloscope, CRO +n03866082 overskirt +n03868242 oxcart +n03868863 oxygen mask +n03871628 packet +n03873416 paddle, boat paddle +n03874293 paddlewheel, paddle wheel +n03874599 padlock +n03876231 paintbrush +n03877472 pajama, pyjama, pj's, jammies +n03877845 palace +n03884397 panpipe, pandean pipe, syrinx +n03887697 paper towel +n03888257 parachute, chute +n03888605 parallel bars, bars +n03891251 park bench +n03891332 parking meter +n03895866 passenger car, coach, carriage +n03899768 patio, terrace +n03902125 pay-phone, pay-station +n03903868 pedestal, plinth, footstall +n03908618 pencil box, pencil case +n03908714 pencil sharpener +n03916031 perfume, essence +n03920288 Petri dish +n03924679 photocopier +n03929660 pick, plectrum, plectron +n03929855 pickelhaube +n03930313 picket fence, paling +n03930630 pickup, pickup truck +n03933933 pier +n03935335 piggy bank, penny bank +n03937543 pill bottle +n03938244 pillow +n03942813 ping-pong ball +n03944341 pinwheel +n03947888 pirate, pirate ship +n03950228 pitcher, ewer +n03954731 plane, carpenter's plane, woodworking plane +n03956157 planetarium +n03958227 plastic bag +n03961711 plate rack +n03967562 plow, plough +n03970156 plunger, plumber's helper +n03976467 Polaroid camera, Polaroid Land camera +n03976657 pole +n03977966 police van, police wagon, paddy wagon, patrol wagon, wagon, black Maria +n03980874 poncho +n03982430 pool table, billiard table, snooker table +n03983396 pop bottle, soda bottle +n03991062 pot, flowerpot +n03992509 potter's wheel +n03995372 power drill +n03998194 prayer rug, prayer mat +n04004767 printer +n04005630 prison, prison house +n04008634 projectile, missile +n04009552 projector +n04019541 puck, hockey puck +n04023962 punching bag, punch bag, punching ball, punchball +n04026417 purse +n04033901 quill, quill pen +n04033995 quilt, comforter, comfort, puff +n04037443 racer, race car, racing car +n04039381 racket, racquet +n04040759 radiator +n04041544 radio, wireless +n04044716 radio telescope, radio reflector +n04049303 rain barrel +n04065272 recreational vehicle, RV, R.V. +n04067472 reel +n04069434 reflex camera +n04070727 refrigerator, icebox +n04074963 remote control, remote +n04081281 restaurant, eating house, eating place, eatery +n04086273 revolver, six-gun, six-shooter +n04090263 rifle +n04099969 rocking chair, rocker +n04111531 rotisserie +n04116512 rubber eraser, rubber, pencil eraser +n04118538 rugby ball +n04118776 rule, ruler +n04120489 running shoe +n04125021 safe +n04127249 safety pin +n04131690 saltshaker, salt shaker +n04133789 sandal +n04136333 sarong +n04141076 sax, saxophone +n04141327 scabbard +n04141975 scale, weighing machine +n04146614 school bus +n04147183 schooner +n04149813 scoreboard +n04152593 screen, CRT screen +n04153751 screw +n04154565 screwdriver +n04162706 seat belt, seatbelt +n04179913 sewing machine +n04192698 shield, buckler +n04200800 shoe shop, shoe-shop, shoe store +n04201297 shoji +n04204238 shopping basket +n04204347 shopping cart +n04208210 shovel +n04209133 shower cap +n04209239 shower curtain +n04228054 ski +n04229816 ski mask +n04235860 sleeping bag +n04238763 slide rule, slipstick +n04239074 sliding door +n04243546 slot, one-armed bandit +n04251144 snorkel +n04252077 snowmobile +n04252225 snowplow, snowplough +n04254120 soap dispenser +n04254680 soccer ball +n04254777 sock +n04258138 solar dish, solar collector, solar furnace +n04259630 sombrero +n04263257 soup bowl +n04264628 space bar +n04265275 space heater +n04266014 space shuttle +n04270147 spatula +n04273569 speedboat +n04275548 spider web, spider's web +n04277352 spindle +n04285008 sports car, sport car +n04286575 spotlight, spot +n04296562 stage +n04310018 steam locomotive +n04311004 steel arch bridge +n04311174 steel drum +n04317175 stethoscope +n04325704 stole +n04326547 stone wall +n04328186 stopwatch, stop watch +n04330267 stove +n04332243 strainer +n04335435 streetcar, tram, tramcar, trolley, trolley car +n04336792 stretcher +n04344873 studio couch, day bed +n04346328 stupa, tope +n04347754 submarine, pigboat, sub, U-boat +n04350905 suit, suit of clothes +n04355338 sundial +n04355933 sunglass +n04356056 sunglasses, dark glasses, shades +n04357314 sunscreen, sunblock, sun blocker +n04366367 suspension bridge +n04367480 swab, swob, mop +n04370456 sweatshirt +n04371430 swimming trunks, bathing trunks +n04371774 swing +n04372370 switch, electric switch, electrical switch +n04376876 syringe +n04380533 table lamp +n04389033 tank, army tank, armored combat vehicle, armoured combat vehicle +n04392985 tape player +n04398044 teapot +n04399382 teddy, teddy bear +n04404412 television, television system +n04409515 tennis ball +n04417672 thatch, thatched roof +n04418357 theater curtain, theatre curtain +n04423845 thimble +n04428191 thresher, thrasher, threshing machine +n04429376 throne +n04435653 tile roof +n04442312 toaster +n04443257 tobacco shop, tobacconist shop, tobacconist +n04447861 toilet seat +n04456115 torch +n04458633 totem pole +n04461696 tow truck, tow car, wrecker +n04462240 toyshop +n04465501 tractor +n04467665 trailer truck, tractor trailer, trucking rig, rig, articulated lorry, semi +n04476259 tray +n04479046 trench coat +n04482393 tricycle, trike, velocipede +n04483307 trimaran +n04485082 tripod +n04486054 triumphal arch +n04487081 trolleybus, trolley coach, trackless trolley +n04487394 trombone +n04493381 tub, vat +n04501370 turnstile +n04505470 typewriter keyboard +n04507155 umbrella +n04509417 unicycle, monocycle +n04515003 upright, upright piano +n04517823 vacuum, vacuum cleaner +n04522168 vase +n04523525 vault +n04525038 velvet +n04525305 vending machine +n04532106 vestment +n04532670 viaduct +n04536866 violin, fiddle +n04540053 volleyball +n04542943 waffle iron +n04548280 wall clock +n04548362 wallet, billfold, notecase, pocketbook +n04550184 wardrobe, closet, press +n04552348 warplane, military plane +n04553703 washbasin, handbasin, washbowl, lavabo, wash-hand basin +n04554684 washer, automatic washer, washing machine +n04557648 water bottle +n04560804 water jug +n04562935 water tower +n04579145 whiskey jug +n04579432 whistle +n04584207 wig +n04589890 window screen +n04590129 window shade +n04591157 Windsor tie +n04591713 wine bottle +n04592741 wing +n04596742 wok +n04597913 wooden spoon +n04599235 wool, woolen, woollen +n04604644 worm fence, snake fence, snake-rail fence, Virginia fence +n04606251 wreck +n04612504 yawl +n04613696 yurt +n06359193 web site, website, internet site, site +n06596364 comic book +n06785654 crossword puzzle, crossword +n06794110 street sign +n06874185 traffic light, traffic signal, stoplight +n07248320 book jacket, dust cover, dust jacket, dust wrapper +n07565083 menu +n07579787 plate +n07583066 guacamole +n07584110 consomme +n07590611 hot pot, hotpot +n07613480 trifle +n07614500 ice cream, icecream +n07615774 ice lolly, lolly, lollipop, popsicle +n07684084 French loaf +n07693725 bagel, beigel +n07695742 pretzel +n07697313 cheeseburger +n07697537 hotdog, hot dog, red hot +n07711569 mashed potato +n07714571 head cabbage +n07714990 broccoli +n07715103 cauliflower +n07716358 zucchini, courgette +n07716906 spaghetti squash +n07717410 acorn squash +n07717556 butternut squash +n07718472 cucumber, cuke +n07718747 artichoke, globe artichoke +n07720875 bell pepper +n07730033 cardoon +n07734744 mushroom +n07742313 Granny Smith +n07745940 strawberry +n07747607 orange +n07749582 lemon +n07753113 fig +n07753275 pineapple, ananas +n07753592 banana +n07754684 jackfruit, jak, jack +n07760859 custard apple +n07768694 pomegranate +n07802026 hay +n07831146 carbonara +n07836838 chocolate sauce, chocolate syrup +n07860988 dough +n07871810 meat loaf, meatloaf +n07873807 pizza, pizza pie +n07875152 potpie +n07880968 burrito +n07892512 red wine +n07920052 espresso +n07930864 cup +n07932039 eggnog +n09193705 alp +n09229709 bubble +n09246464 cliff, drop, drop-off +n09256479 coral reef +n09288635 geyser +n09332890 lakeside, lakeshore +n09399592 promontory, headland, head, foreland +n09421951 sandbar, sand bar +n09428293 seashore, coast, seacoast, sea-coast +n09468604 valley, vale +n09472597 volcano +n09835506 ballplayer, baseball player +n10148035 groom, bridegroom +n10565667 scuba diver +n11879895 rapeseed +n11939491 daisy +n12057211 yellow lady's slipper, yellow lady-slipper, Cypripedium calceolus, Cypripedium parviflorum +n12144580 corn +n12267677 acorn +n12620546 hip, rose hip, rosehip +n12768682 buckeye, horse chestnut, conker +n12985857 coral fungus +n12998815 agaric +n13037406 gyromitra +n13040303 stinkhorn, carrion fungus +n13044778 earthstar +n13052670 hen-of-the-woods, hen of the woods, Polyporus frondosus, Grifola frondosa +n13054560 bolete +n13133613 ear, spike, capitulum +n15075141 toilet tissue, toilet paper, bathroom tissue diff --git a/flow-python/setup.py b/flow-python/setup.py index 5c73ec4..d04b3db 100644 --- a/flow-python/setup.py +++ b/flow-python/setup.py @@ -23,7 +23,7 @@ setup( options={ 'bdist_wheel': { - 'python_tag': "py{}{}".format(py_version.major, + 'python_tag': "py{}.{}".format(py_version.major, py_version.minor), } }, diff --git a/flow-rs/src/channel/inner.rs b/flow-rs/src/channel/inner.rs index 7a5615c..ed3eee5 100644 --- a/flow-rs/src/channel/inner.rs +++ b/flow-rs/src/channel/inner.rs @@ -1,13 +1,31 @@ -/** - * \file flow-rs/src/channel/inner.rs - * MegFlow is Licensed under the Apache License, Version 2.0 (the "License") +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 * - * Copyright (c) 2019-2021 Megvii Inc. All rights reserved. + * 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 ARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * 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. */ + +/* + * Copyright (c) https://github.com/smol-rs/async-channel + */ + +/* + * Copyright (c) 2021, megvii. inc + * Author: MegEngine@megvii.com + */ + use std::error; use std::fmt; use std::future::Future; diff --git a/flow-rs/src/channel/receiver.rs b/flow-rs/src/channel/receiver.rs index 364e0f6..a239f8c 100644 --- a/flow-rs/src/channel/receiver.rs +++ b/flow-rs/src/channel/receiver.rs @@ -8,11 +8,14 @@ * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT ARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. */ +use futures_util::stream::FuturesUnordered; +use futures_util::{pin_mut, select, FutureExt, StreamExt}; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::Arc; +use std::time::Duration; use super::inner::Receiver as RecvImpl; -use super::RecvError; +use super::{BatchRecvError, RecvError}; use crate::envelope::{DummyEnvelope, Envelope, SealedEnvelope}; use super::ChannelBase; @@ -112,4 +115,73 @@ impl Receiver { Ok(envelope) } } + + /// Receives some envelopes from the channel. + /// + /// If there are less than n envelopes in the channel, this method waits until there is n envelopes or timeout. + /// + /// If the channel is closed, this method receives n envelopes or returns an error with rest + /// envelopes if there are no enough envelopes. + /// + /// If the channel encounters a flush event, this method returns an error with rest envelopes. + pub async fn batch_recv( + &self, + n: usize, + dur: Duration, + ) -> Result>, BatchRecvError>> + where + T: 'static + Send + Clone, + { + let convert = |envelopes: Vec| { + envelopes + .into_iter() + .map(|mut envelope| { + envelope + .downcast_mut::>() + .expect("type error when downcast") + .take() + }) + .collect() + }; + let batch_any = self.batch_recv_any(n, dur).await; + batch_any.map(convert).map_err(|err| match err { + BatchRecvError::Closed(envelopes) => BatchRecvError::Closed(convert(envelopes)), + }) + } + + /// Receives some any envelopes from the channel, see document of `batch_recv` for more detail + pub async fn batch_recv_any( + &self, + n: usize, + dur: Duration, + ) -> Result, BatchRecvError> { + let mut wait_recv = FuturesUnordered::new(); + wait_recv.push(self.recv_any()); + let timeout = crate::rt::task::sleep(dur).fuse(); + let mut batch = vec![]; + pin_mut!(timeout); + loop { + select! { + _ = timeout => { + return Ok(batch); + } + msg = wait_recv.select_next_some() => { + match msg { + Ok(msg) => { + batch.push(msg); + if batch.len() == n { + return Ok(batch); + } else { + wait_recv.push(self.recv_any()); + } + }, + Err(_) => { + return Err(BatchRecvError::Closed(batch)); + } + } + + } + } + } + } } diff --git a/flow-rs/src/debug/feature.rs b/flow-rs/src/debug/feature.rs index fa5cb3d..6881b79 100644 --- a/flow-rs/src/debug/feature.rs +++ b/flow-rs/src/debug/feature.rs @@ -12,7 +12,7 @@ use event_listener::Event; use std::sync::atomic::{AtomicBool, Ordering}; pub struct Feature { - pub enable: AtomicBool, + enable: AtomicBool, lock_ops: Event, } @@ -24,8 +24,22 @@ impl Feature { } } + pub fn enable(&self) -> bool { + self.enable.load(Ordering::Relaxed) + } + + pub fn disable(&self) { + self.enable.store(false, Ordering::Relaxed); + } + pub fn notify(&self) { - self.lock_ops.notify(usize::MAX); + if self + .enable + .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst) + .is_ok() + { + self.lock_ops.notify(usize::MAX); + } } #[allow(dead_code)] pub async fn wait(&self) { diff --git a/flow-rs/src/graph/debug.rs b/flow-rs/src/graph/debug.rs index 02b44a0..259cd9e 100644 --- a/flow-rs/src/graph/debug.rs +++ b/flow-rs/src/graph/debug.rs @@ -11,9 +11,9 @@ use super::Graph; use crate::debug::*; use crate::rt::task::JoinHandle; +use futures_util::{pin_mut, select, FutureExt}; use serde::Serialize; use std::collections::HashMap; -use std::sync::atomic::Ordering; #[derive(Serialize)] struct NodeQps { @@ -25,14 +25,22 @@ struct NodeQps { impl Graph { pub(super) fn dmon(&self) -> JoinHandle<()> { let conns: Vec<_> = self.conns.values().cloned().collect(); - let graph_ty = self.ctx.ty.clone(); - let mut first = true; + let ctx = self.ctx.clone(); + let mut first = true; // drop qps result fetched first crate::rt::task::spawn(async move { - loop { - while !QPS.enable.load(Ordering::Relaxed) { + let wait_graph = ctx.wait().fuse(); + pin_mut!(wait_graph); + 'start: while !ctx.is_closed() { + if !QPS.enable() { first = true; - QPS.wait().await; } + let wait_qps = QPS.wait().fuse(); + pin_mut!(wait_qps); + select! { + _ = wait_qps => {}, + _ = wait_graph => break 'start, + } + let args = QPS_args .read() .unwrap() @@ -89,7 +97,7 @@ impl Graph { }; let args = into_object(serde_json::json!({ - "graph": graph_ty, + "graph": ctx.ty, "nodes": nodes.into_iter().map(|(_, v)| v).collect::>(), })); diff --git a/flow-rs/src/graph/mod.rs b/flow-rs/src/graph/mod.rs index 9074454..f9ac5ea 100644 --- a/flow-rs/src/graph/mod.rs +++ b/flow-rs/src/graph/mod.rs @@ -33,7 +33,6 @@ crate::collect!(String, GraphSlice); /// Represents a graph with nodes and connections, which implement `Node` and `Actor`. pub struct Graph { - main: bool, nodes: HashMap, conns: HashMap, inputs: Vec, @@ -41,6 +40,7 @@ pub struct Graph { broker: Broker, shares: HashMap, ctx: Context, + global_ctx: Option, resources: ResourceCollection, } @@ -156,9 +156,9 @@ impl Graph { } Ok(Graph { + global_ctx: None, ctx, resources, - main: false, nodes, conns, broker, @@ -197,8 +197,8 @@ impl Graph { } } - let is_main = self.main; let context = self.ctx.clone(); + let global_ctx = self.global_ctx.clone(); let inputs: Vec<_> = self .inputs .iter() @@ -222,21 +222,21 @@ impl Graph { } } - for task in alone_tasks { - task.cancel().await; - } + futures_util::future::join_all(alone_tasks).await; - if is_main { + if let Some(global_ctx) = global_ctx { SharedProxy::registry_local() .get(context.local_key) .for_each(|proxy| proxy.close()); - let handles = crate::node::SharedStopNotify::registry_local() + global_ctx.close(); + + let handles = crate::node::SharedHandle::registry_local() .get(context.local_key) .to_vec(); for handle in handles { let handle = std::sync::Arc::try_unwrap(handle) .unwrap_or_else(|_| panic!("internal error")); - handle.0.await.ok(); + handle.0.await; } // clear graph local resources finalize(context.local_key); @@ -256,7 +256,7 @@ impl Graph { self.close() } - pub(crate) fn mark_main(&mut self) { + pub(crate) fn mark_main(&mut self, global_ctx: Context) { let mut v = vec![]; for name in &self.inputs { v.push((name.clone(), self.conns[name].make())); @@ -269,6 +269,6 @@ impl Graph { self.set_port(name.as_str(), None, &conn); } - self.main = true; + self.global_ctx = Some(global_ctx); } } diff --git a/flow-rs/src/graph/subgraph.rs b/flow-rs/src/graph/subgraph.rs index 33acba7..bbc847e 100644 --- a/flow-rs/src/graph/subgraph.rs +++ b/flow-rs/src/graph/subgraph.rs @@ -52,10 +52,8 @@ impl Node for Graph { } fn close(&mut self) { - if self.main { - for input in &self.inputs { - self.conns[input].get().close(); - } + for input in &self.inputs { + self.conns[input].get().close(); } } @@ -74,12 +72,6 @@ impl Actor for Graph { context: Context, resource: ResourceCollection, ) -> JoinHandle<()> { - let inputs: Vec<_> = self - .inputs - .iter() - .map(|name| self.conns[name].get().clone()) - .collect(); - flow_rs::rt::task::spawn(async move { let wait_ctx = context.wait().fuse(); let wait_graph = self.as_mut().start(Some(resource)).fuse(); @@ -87,9 +79,7 @@ impl Actor for Graph { loop { select! { _ = wait_ctx => { - for input in &inputs { - input.close(); - } + self.close() } _ = wait_graph => context.close(), complete => break, diff --git a/flow-rs/src/lib.rs b/flow-rs/src/lib.rs index c88a981..4c317fb 100644 --- a/flow-rs/src/lib.rs +++ b/flow-rs/src/lib.rs @@ -154,7 +154,7 @@ fn load_impl( ); node::SharedProxy::registry_local().get(local_key).insert( cfg.entity.name.clone(), - node::load_shared(cfg, ctx.clone(), res)?, + node::load_shared(cfg, &config, ctx.clone(), res)?, ); } @@ -164,7 +164,7 @@ fn load_impl( .map(|slice| (slice.cons)(config.main.clone())) { Some(mut ret) => { - let _ = ret.as_mut().map(|g| g.mark_main()); + let _ = ret.as_mut().map(|g| g.mark_main(ctx)); ret } _ => Err(anyhow!("graph {} is not exist", config.main)), diff --git a/flow-rs/src/loader/python/channel.rs b/flow-rs/src/loader/python/channel.rs index 30bbdab..47c0cf0 100644 --- a/flow-rs/src/loader/python/channel.rs +++ b/flow-rs/src/loader/python/channel.rs @@ -10,10 +10,10 @@ */ use super::context::with_context; use super::envelope::PyEnvelope; -use crate::channel::{Receiver, Sender}; +use crate::channel::{BatchRecvError, Receiver, Sender}; use pyo3::prelude::*; use stackful::wait; -use std::sync::Arc; +use std::{sync::Arc, time::Duration}; #[pyclass] pub(crate) struct PySender { @@ -43,4 +43,26 @@ impl PyReceiver { _ => py.None(), } } + + fn batch_recv(&self, py: Python, n: usize, dur: u64) -> (Vec, bool) { + let convert = |envelope| { + Py::new( + py, + PyEnvelope { + imp: Some(envelope), + }, + ) + .unwrap() + .to_object(py) + }; + match with_context(py, || { + wait( + self.imp + .batch_recv::(n, Duration::from_millis(dur)), + ) + }) { + Ok(msg) => (msg.into_iter().map(convert).collect(), false), + Err(BatchRecvError::Closed(msg)) => (msg.into_iter().map(convert).collect(), true), + } + } } diff --git a/flow-rs/src/node/shared.rs b/flow-rs/src/node/shared.rs index f18630e..ba095d8 100644 --- a/flow-rs/src/node/shared.rs +++ b/flow-rs/src/node/shared.rs @@ -31,10 +31,10 @@ struct Shared { outputs: HashMap, } -pub struct SharedStopNotify(pub oneshot::Receiver>); -// is safe because we visit it only in main thread -unsafe impl Sync for SharedStopNotify {} -crate::collect!(String, SharedStopNotify); +pub struct SharedHandle(pub rt::task::JoinHandle<()>); +// is safe because we visit it only in single thread +unsafe impl Sync for SharedHandle {} +crate::collect!(String, SharedHandle); #[derive(Clone)] pub struct SharedProxy { @@ -50,21 +50,54 @@ impl Shared { local_key: u64, rx: ReceiverT, cfg: &crate::config::interlayer::Node, + graphs: &crate::config::interlayer::Config, ) -> Result { let mut nodes = load(local_key, cfg)?; let mut inputs = HashMap::new(); let mut outputs = HashMap::new(); - for input in super::inputs(local_key, &cfg.entity.ty)? { - let chan = ChannelStorage::unbound(); + let find_cap = + |port| { + let mut cap = 0usize; + for graph in graphs + .graphs + .iter() + .filter(|graph| !graph.nodes.keys().any(|node| node == &cfg.entity.name)) + { + cap = + std::cmp::max( + cap, + graph + .connections + .values() + .map(|conn| { + if conn.rx.iter().chain(conn.tx.iter()).any(|p| { + p.node_name == cfg.entity.name && &p.port_name == port + }) { + conn.cap + } else { + 0 + } + }) + .max() + .expect("unused port in shared nodes"), + ); + } + cap + }; + + for input in cfg.inputs.keys() { + let cap = find_cap(input); + let chan = ChannelStorage::bound(cap); for node in &mut nodes { node.set_port(input.as_str(), None, &chan); } inputs.insert(input.clone(), Arc::new(chan.sender())); } - for output in super::outputs(local_key, &cfg.entity.ty)? { - let chan = ChannelStorage::unbound(); + for output in cfg.outputs.keys() { + let cap = find_cap(output); + let chan = ChannelStorage::bound(cap); for node in &mut nodes { node.set_port(output.as_str(), None, &chan); } @@ -239,17 +272,16 @@ crate::collect!(String, SharedProxy); pub fn load_shared( cfg: &crate::config::interlayer::Node, + graphs: &crate::config::interlayer::Config, ctx: Context, resources: ResourceCollection, ) -> Result { let local_key = ctx.local_key; let (s, r) = unbounded(); - let shared = Shared::new(ctx.local_key, r, cfg)?.boxed(); + let shared = Shared::new(ctx.local_key, r, cfg, graphs)?.boxed(); let handle = shared.start(ctx, resources); - let (s2, r2) = oneshot::channel(); - s2.send(handle).ok(); - SharedStopNotify::registry_local() + SharedHandle::registry_local() .get(local_key) - .insert(cfg.entity.name.clone(), SharedStopNotify(r2)); + .insert(cfg.entity.name.clone(), SharedHandle(handle)); SharedProxy::new(local_key, s, cfg) } diff --git a/flow-rs/tests/04-isolated.rs b/flow-rs/tests/04-isolated.rs new file mode 100644 index 0000000..30f300b --- /dev/null +++ b/flow-rs/tests/04-isolated.rs @@ -0,0 +1,38 @@ +mod nodes_ext; + +use anyhow::Result; +use flow_rs::prelude::*; + +#[rt::test] +async fn test_isolated() -> Result<()> { + let mut graph = load( + None, + r#" +main="test" +[[graphs]] +name="test" +nodes=[ + {name="a", ty="Isolated"}, +] + "#, + )?; + graph.start(None).await; + Ok(()) +} + +#[rt::test] +async fn test_isolated_in_global() -> Result<()> { + let mut graph = load( + None, + r#" +main="test" +[[nodes]] +name="a" +ty="IsolatedNever" +[[graphs]] +name="test" + "#, + )?; + graph.start(None).await; + Ok(()) +} diff --git a/flow-rs/tests/nodes_ext.rs b/flow-rs/tests/nodes_ext.rs index d5931ab..f56cd2f 100644 --- a/flow-rs/tests/nodes_ext.rs +++ b/flow-rs/tests/nodes_ext.rs @@ -81,3 +81,42 @@ impl NeverOpr { } node_register!("NeverOpr", NeverOpr); + +#[inputs] +#[outputs] +#[derive(Node, Actor, Default)] +struct Isolated {} + +impl Isolated { + fn new(_name: String, _: &Table) -> Self { + Default::default() + } + + async fn initialize(&mut self, _: ResourceCollection) {} + async fn finalize(&mut self) {} + async fn exec(&mut self, _: &Context) -> Result<()> { + Ok(()) + } +} + +node_register!("Isolated", Isolated); + +#[inputs] +#[outputs] +#[derive(Node, Actor, Default)] +struct IsolatedNever {} + +impl IsolatedNever { + fn new(_name: String, _: &Table) -> Self { + Default::default() + } + + async fn initialize(&mut self, _: ResourceCollection) {} + async fn finalize(&mut self) {} + async fn exec(&mut self, ctx: &Context) -> Result<()> { + ctx.wait().await; + Ok(()) + } +} + +node_register!("IsolatedNever", IsolatedNever); From 5c501ff6aaad43b635cad786ceb6095b1667d9bb Mon Sep 17 00:00:00 2001 From: MegEngine Date: Tue, 14 Sep 2021 20:16:47 +0800 Subject: [PATCH 2/2] docs(project): remove useless --- README.md | 1 - docs/how-to-build-and-run/.gitattributes | 1 - 2 files changed, 2 deletions(-) delete mode 100644 docs/how-to-build-and-run/.gitattributes diff --git a/README.md b/README.md index da96848..b491d82 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,6 @@ MegFlow 是一个面向视觉应用的流式计算框架, 目标是简单、高 ## HowTo * how to build and run - * [run with prebuilt .whl](docs/how-to-build-and-run/run-in-15-minutes.zh.md) * [build with docker](docs/how-to-build-and-run/build-with-docker.zh.md) * [build from source](docs/how-to-build-and-run/build-from-source.zh.md) * [generate rtsp](docs/how-to-build-and-run/generate-rtsp.zh.md) diff --git a/docs/how-to-build-and-run/.gitattributes b/docs/how-to-build-and-run/.gitattributes deleted file mode 100644 index 30dd606..0000000 --- a/docs/how-to-build-and-run/.gitattributes +++ /dev/null @@ -1 +0,0 @@ -workflow.png filter=lfs diff=lfs merge=lfs -text