我经常被问到这类问题:

  • 关掉浏览器,Notebook 中的训练还在运行吗?
  • 刚刚电脑断网了,网页重新连接后为什么训练停止了?
  • 我显示器关了,服务器上的代码还在跑吗?

先说结论:服务器上 Notebook 的运行状态和用户的电脑的状态没有任何关系,所以不管用户电脑是关掉浏览器或显示器,拔掉网线或电源线,服务器上的训练依然还在进行着(Google Colab 的 Notebook 除外)。

本文就简单说一说 Jupyter Notebook 的运行机制,并由此引出一些好的实践,相信读者读完本文后会对 Jupyter Notebook 有更进一步的认识。

Jupyter Notebook 是如何工作的

先放一张图:

chart-1

Jupyter Notebook 有三个重要的组建:

  • 前端页面,就是用户所看到的界面
  • API Gateway,负责收发请求
  • Kernel,这是真正运行用户代码的模块

一段代码执行的过程是:

  1. 用户发送请求到 Jupyter 的 API Gateway
  2. Gateway 内部负责将该请求转发给某一个 Kernel
  3. Kernel 开始运行请求中的代码,并返回结果给 Gateway,Gateway 再把结果返回给浏览器(用户)

由此可知,真正运行代码的组建是 Kernel,而 Kernel 是运行在服务器中的,因此客户端浏览器关闭与否根本不会影响的 Kernel 的运行状态。

但是为什么有很多人认为 Jupyter Notebook 在刷新页面后,程序就被停止了呢?

运行状态

上文所说的三个组建中,有两个都是带状态的:前端页面(浏览器),Kernel(运行时),我们先说 Kernel 的状态。

Kernel 状态

Kernel 状态就是 Python 进程的状态(这里都以 Python 的 Kernel 为例子),那么 Python 进程有可能正在运行某段代码,则他处理繁忙的状态;有可能没有在运行,则处于闲置的状态。Kernel 的状态只跟用户请求有关,用户发送运行代码的请求,则 Kernel 开始运行,用户点击「重启 Kernel 的按钮」,则 Kernel 对应的 Python 进程会重启。而关闭浏览器等操作不会发送任何请求,因此这些操作也不会影响 Kernel 的状态。

而导致用户认为「程序被停止」的原因,是浏览器的状态在刷新页面后会被清空,那这里浏览器的状态是指什么呢?

gif1

浏览器的状态

Jupyter Notebook 的大多数输出内容,都是通过浏览器脚本(JavaScript)动态渲染出来的,包括但不限于:

  1. 某个正在运行的 Cell 的输出
  2. tqdm 进度条
  3. 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}")