syf 发布的文章

在阿里云 vgn5i 实例上部署深度学习环境,服务器系统为 Ubuntu 16.04

1. 安装 GRID 驱动

https://help.aliyun.com/document_detail/118852.html

按照此文档安装 GRID 驱动,注意 vGPU 需要使用专用的 GRID 驱动,安装普通驱动是无法识别虚拟卡的,且安装完驱动之后需要按照文档获得许可才可以实际使用 CUDA。在没有获得许可之前,虽然torch.cuda.is_available()返回的是True,但实际上到了代码中model.to(cuda)这一步还是会报RuntimeError: CUDA error: all CUDA-capable devices are busy or unavailable错误。

2. 使用 systemd + gunicorn 部署项目

这一步创建了个没有 sudo 权限的用户ob来跑项目,保证生产环境上服务器安全。代码放在/home/ob/ob_cuda_server目录下。坑点在于 systemd 中 anaconda 环境的切换,解决办法就是在 service 配置里指定环境变量 PATH 就直接是对应的 conda 下的 env/bin 目录,就不会用冲突了。

2.1 安装项目依赖

按照此文档下载 anaconda 并配置国内源

https://mirrors.tuna.tsinghua.edu.cn/help/anaconda/

创建虚拟环境:

conda create -n ob_prod python=3.6

切换到此环境:

conda activate ob_prod

通过 conda 安装 torch:

conda install pytorch torchvision cudatoolkit=10.1 -c pytorch

通过 pip 安装其他依赖:

pip install flask opencv-python numpy

其中 opencv-python 可能会缺少一些系统 so 库,稍后运行程序时如果提示缺少某 lib 文件则需要使用 apt 安装对应的库。

2.2 配置生产环境

安装 gunicorn 进行生产环境部署以提供更好的性能:

pip install gunicorn

安装好之后可以使用gunicorn -w 8 -b 0.0.0.0:9001 main:app来部署项目看看效果

另开一个会话使用 curl 测试服务是否在线:

(base) syf@obulb:~$ curl localhost:9001
ob cuda server online!

可以看到已经返回了自定义的首页信息

确认可以运行后按Ctrl+C停止,后面用 systemd 来启动管理守护进程。

为了守护项目进程,让其后台自启运行,需要写一个 service 配置将 gunicorn 写入 systemd 服务。

新建/etc/systemd/system/ob_cuda并编辑:

[Unit]
Description=ob cuda server
After=network.target

[Service]
User=ob
WorkingDirectory=/home/ob/ob_cuda_server
Environment="PATH=/home/ob/anaconda3/envs/ob_prod/bin"
ExecStart=/home/ob/anaconda3/envs/ob_prod/bin/gunicorn -w 8 -b 0.0.0.0:9001 main:app
# gunicorn 多线程加载模型可能会造成显存不足或其他问题,如果用此部署后有问题则直接跑 flask,就可以解决:
# ExecStart=/home/ob/anaconda3/envs/ob_prod/bin/python main.py

[Install]
WantedBy=multi-user.target

保存后重新加载 systemd:

sudo systemctl daemon-reload

写成 systemd 服务后就可以直接用 systemctl 来管理服务了:

设置开机启动:

systemctl enable ob_cuda

查看服务状态:

systemctl status ob_cuda

重启服务:

systemctl restart ob_cuda

对 python 的 Flask、FastAPI 和 Spring Boot 集成的简单接口做速度对比,返回 json 格式的当前时间戳:

{
    "timestamp":1584416801569
}

同时把某个11位时间戳 json string 写死在 index.html 里,测试 Nginx 的并发量。虽然一个静态资源一个动态计算没有可比性,但是为了体现性能差距,还是把 Nginx 的并发拿来做对比。

- 阅读剩余部分 -

不知从什么时候开始,shadowsocks 开启后一直无法监听 1080 端口,当时换了 1081、1082 也不行,于是换了个比较高的 10800 端口解决了,也没当回事。今天因为要跑一个非 maven 的项目,需要在 idea 里手动配置 tomcat,默认的 JMX 端口 1099 又没法分配,而使用netstat -ano | grep "1099"又显示没有任何程序在占用这个端口,非常诡异。后来更改为 1098、1097 也是和上次 shadowsocks 同样的情况,死活绑定不上去,而 netstat 又显示没有程序占用这个端口,换成比较高的比如 11099 又可用了……

- 阅读剩余部分 -

使用 Spring Boot 接收上传的文件,接收到的文件对象为srcFile,目标路径为 /upload/img/fileName.jpg,使用transferTo方法存储到本地:

File dstFile = new File("/upload/img/" + fileName);
srcFile.transferTo(dstFile);

提示目标文件路径不存在:

java.io.IOException: java.io.FileNotFoundException: C:\Users\syf\AppData\Local\Temp\tomcat.6910981657131718308.8080\work\Tomcat\localhost\ROOT\upload\img\5a6acf84e27448be9925959821ca1e90.png (系统找不到指定的路径。)

可以看见封装的transferTo方法自动把相对路径补全到了绝对路径,且是以 Tomcat 容器所在的临时目录为父目录补全的,而我们是想保存到项目的/upload/img/目录下。

解决方法:

不使用MultipartFile.transferTo,把 MultipartFile 对象转字节流后使用 FileOutputStream 手动保存二进制流:

// 这里的actualPath是相对项目根目录的路径
String actualPath = "src/main/webapp/upload/img/" + fileName;
String absolutePath = "/upload/img/" + fileName;

try {
    byte[] srcFileBytes = srcFile.getBytes();
    OutputStream f = new FileOutputStream(actualPath);
    f.write(srcFileBytes);
    f.close();
} catch (FileNotFoundException e) {
    System.out.println("File not found");
    System.out.println(actualPath);
    e.printStackTrace();
    return null;
} catch (IOException e) {
    e.printStackTrace();
    return null;
}
return absolutePath;

The Pale Blue Dot

这张照片是卡尔萨根说服整个 NASA,让航天器转个圈拍的一张地球照片,叫做“The Pale Blue Dot”

这是从40亿英里外的太空拍摄的地球照片,太阳光束上的那一个小点就是我们的家

- 阅读剩余部分 -