Jupyter Notebook 的运行机制
我经常被问到这类问题:
- 关掉浏览器,Notebook 中的训练还在运行吗?
- 刚刚电脑断网了,网页重新连接后为什么训练停止了?
- 我显示器关了,服务器上的代码还在跑吗?
先说结论:服务器上 Notebook 的运行状态和用户的电脑的状态没有任何关系,所以不管用户电脑是关掉浏览器或显示器,拔掉网线或电源线,服务器上的训练依然还在进行着(Google Colab 的 Notebook 除外)。
本文就简单说一说 Jupyter Notebook 的运行机制,并由此引出一些好的实践,相信读者读完本文后会对 Jupyter Notebook 有更进一步的认识。
Jupyter Notebook 是如何工作的
先放一张图:
Jupyter Notebook 有三个重要的组建:
- 前端页面,就是用户所看到的界面
- API Gateway,负责收发请求
- Kernel,这是真正运行用户代码的模块
一段代码执行的过程是:
- 用户发送请求到 Jupyter 的 API Gateway
- Gateway 内部负责将该请求转发给某一个 Kernel
- Kernel 开始运行请求中的代码,并返回结果给 Gateway,Gateway 再把结果返回给浏览器(用户)
由此可知,真正运行代码的组建是 Kernel,而 Kernel 是运行在服务器中的,因此客户端浏览器关闭与否根本不会影响的 Kernel 的运行状态。
但是为什么有很多人认为 Jupyter Notebook 在刷新页面后,程序就被停止了呢?
运行状态
上文所说的三个组建中,有两个都是带状态的:前端页面(浏览器),Kernel(运行时),我们先说 Kernel 的状态。
Kernel 状态
Kernel 状态就是 Python 进程的状态(这里都以 Python 的 Kernel 为例子),那么 Python 进程有可能正在运行某段代码,则他处理繁忙的状态;有可能没有在运行,则处于闲置的状态。Kernel 的状态只跟用户请求有关,用户发送运行代码的请求,则 Kernel 开始运行,用户点击「重启 Kernel 的按钮」,则 Kernel 对应的 Python 进程会重启。而关闭浏览器等操作不会发送任何请求,因此这些操作也不会影响 Kernel 的状态。
而导致用户认为「程序被停止」的原因,是浏览器的状态在刷新页面后会被清空,那这里浏览器的状态是指什么呢?
浏览器的状态
Jupyter Notebook 的大多数输出内容,都是通过浏览器脚本(JavaScript)动态渲染出来的,包括但不限于:
- 某个正在运行的 Cell 的输出
- tqdm 进度条
- plotly 的一些可交互图标
- …
一旦刷新页面后,这些动态产生的内容都会丢失,只保留静态的内容,因此会给人一种「程序停止」的错觉。
通过下面的代码可以很容易证明「进度条虽然停止,但程序还在运行」:
import time
from tqdm import tqdm
f = open("/tmp/log.txt", "w")
for i in tqdm(range(100)):
f.write("I'm alive\n")
f.flush()
time.sleep(1)
f.close()
在任意一个单元格中运行下面的代码,刷新页面后进度条会停止滚动,但如果在终端中运行 tail -f /tmp/log.txt
会发现不断有新的「I’m alive」输出,说明程序是在运行的。
实际上, Jupyter 官方的开发者也在尝试保留浏览器的状态,也就是刷新浏览器后可以恢复之前的所有内容并持续输出,参考 GitHub Issue。
最佳实践
永远不要依赖 Jupyter Notebook 的输出,因为我们无法知道今晚会不会停电。把所有的日志输出到日志文件里是最稳妥的做法。利用 Python 的 logging
模块很容易实现。
import logging
logging.basicConfig(level=logging.DEBUG,
format='%(asctime)s %(message)s',
handlers=[logging.FileHandler("log.txt"),
logging.StreamHandler()])
# somewhere in your code....
logging.info(f"The accuracy score is {score}")