JC's FSS
JC's FSS

  • 首页

  • 归档

  • 分类

  • 标签

Wifite+hashcat破解WPA2 Wi-Fi密码

发表于 2020-07-19 | 分类于 技术 | 评论数: | 阅读次数:
本文字数: 7k | 阅读时长 ≈ 13 分钟

博主的一些废话

不知不觉已经有一年没有更新博客了。之前关注我博客的读者们,看到这次更新一定很惊讶。感谢你们的订阅和关注,本站会继续介绍和讨论各种有趣又有用的技术话题。
首先想替自己小小的辩解一下,这一年间我也并不是完全闲着,之前想本着工作和兴趣结合的原则,做一系列DDR相关的内容,甚至像模像样的还写了一些内容,截图为证:

但是后来转念一想,对芯片方面感兴趣的读者本来就偏少,并且主题的内容似乎欠缺了一些趣味性,写起来又费劲,于是就弃了坑。在此之后世界各地都出现了以前漫画里才敢想象的事件,突然之间这个小小的博客显得有点不足为道。不过随着慢慢的学会了接受一些现实的疯狂,博主这段时间又找到了新的灵感,最近又重新提起键盘,整装上阵。

Wi-Fi安全历史 [The history of Wi-Fi security]

今天这篇博文要讲的是一个新颖的老话题,Wi-Fi安全。自从1999年WECA(Wireless Ethernet Compatibility Alliance, 无线以太网兼容联盟,现Wi-Fi联盟)制定第一代IEEE 802.11标准以来,围绕Wi-Fi密码的攻防就从来没有停止过。在最开始,大家的目的都还比较单纯,那个时候移动数据流量很贵,速度又很慢,攻击Wi-Fi密码的最主要动机是“蹭网”。后来随着网络在生活中的重要性逐渐增加,破解Wi-Fi成为了一种攻击面较广的入侵内网方式,攻破Wi-Fi密码的“奖品”也加码了不少:释放勒索病毒、拦截未加密的通信,这些可都是实打实的经济利益。
目前Wi-Fi常用的安全认证协议有WEP和WPA两大类。其中,由于设计上的失误,WEP(Wired Equivalent Privacy)被公认为是一种安全不及格的认证方式,关于WEP糟糕的安全性可以参考这个【Wikipedia页面】。在今天,WEP认证能够提供的安全性和“此地无银三百两”的牌子差不多,也正因如此,现在很多新款的无线路由器都不再提供WEP协议了。
WPA则是Wi-Fi Protected Access的简写,意为“受保护的Wi-Fi访问”,自2003年首次提出以来,WPA认证协议至今已经发展到第三代。第一代WPA使用了一种叫TKIP(Temporal Key Integrity Protocol, 临时密钥完整性协议)的加密方式,因此又被称为WPA with TKIP。由于兼容性方面的考虑,WPA with TKIP在设计上有许多借鉴WEP的地方,也正因如此它在安全性上也不尽如人意。
与WPA一同诞生的还有WPA2,两者最主要的区别是,WPA2强制要求使用AES加密,提供了大大超过TKIP的安全性,现在普遍的共识是使用强度合格密码的WPA2可以提供远超过大多数用户需求的安全性。随着Wi-Fi的普及和信息安全重要性的提高,目前大部分Wi-Fi设备都已经具备了AES加解密硬件加速的能力,WPA2也已经是大多数路由器的默认选项。
2018年1月,Wi-Fi联盟提出了WPA3认证协议,只可惜到今天为止,WPA3的普及程度还非常低,连Windows 10都要到2004版才能支持,能够支持WPA3的路由器更是屈指可数。
值得一提的是,随着WPA一同提出的还有一个叫做WPS(Wi-Fi Protected Setup)的附加功能。通俗的来讲,WPS是想把一个较长、较难记忆的密码浓缩成一个8位纯数字的PIN,但是由于设计上的失误(这句话是不是有点眼熟了),WPS的密码空间远远达不到8位数字能够提供的全部密码空间,关于这个问题的详细原理可以参考【这个】Wikipedia页面。
根据上面的介绍,我们可以得出这样一个结论:如果普通用户想保证自己的WiFi安全,只需要做到这三点即可:1. 使用较强的密码; 2. 通过WPA2认证协议加密; 3. 关闭WPS功能。
还可以得出一个附加结论:Wi-Fi联盟那帮人真的是有点菜。

WPA2认证协议破解思路 [The principle of WPA2 hacking]

细心的读者读到这里可能会有一个疑问:既然上一节已经提到WPA2具有足够的安全性,那又谈何破解一说呢?正如上一节最后所述,安全性是建立在【1. 使用较强的密码; 2. 通过WPA2认证协议加密; 3. 关闭WPS功能。】这三个前提上的,缺一不可。其中,由于现在大多数路由器的默认配置就是WPA2认证,关闭WPS,所以2、3两点条件是最容易满足的。鉴于此,今天这篇博文主要讨论的攻击场景假设被攻击的Wi-Fi使用WPA2认证协议,并且关闭WPS的情况。这种攻击场景也是被认为最难的一种攻击场景,如果读者们对WEP/WPA1认证或开启WPS的情况也比较感兴趣的话可以在留言区里留言,如果对这个话题感兴趣的读者较多的话可以考虑未来再开一篇博文进行讨论。
而上一小节讲的第1个条件,则是大多数用户最容易疏忽的地方。为了方便记忆,大多数人可能会为许多不同的账号设置相同的密码,或者使用类似12345678这样规律简单的序列,这就给了攻击者可乘之机。
WPA2的破解过程主要分为两步:1. 抓取WPA2的四重握手(4-Way Handshake)认证包;2. 破解四重握手认证包。
假设被攻击的Wi-Fi接入点为A,接入点中有一个合法的Wi-Fi客户端B,攻击者为C,则抓取WPA2四重握手信息的主要步骤为:

  1. 攻击者C使其Wi-Fi收发设备进入监视模式(Monitor Mode),嗅探周围存在的Wi-Fi接入点、Wi-Fi客户端的互相连接信息和MAC地址;
  2. 攻击者C将自己的MAC地址伪装成Wi-Fi接入点A的MAC信息,并向合法客户端B发出断线信息;
  3. 合法客户端B误以为自己断线,重新和接入点A进行四重握手认证流程;
  4. 攻击者C截取四重握手认证包。

这部分内容其实叫做KRACK(Key Reinstallation Attacks, 密钥重载攻击),于2017年由比利时鲁汶大学的研究者们提出。对这部分内容感兴趣的读者们可以在【他们的主页】找到更详细的描述。

破解四重握手认证包的主要步骤为:

  1. 获取一个质量较高的密码字典,最理想的情况是该字典包含被攻击的四重握手包密码,其次理想的情况是被攻击的密码大部分字符出现在字典中,剩下字符则是规则简单的序列;
  2. 使用GPU加速hash碰撞,进行密码破解。

对这部分内容感兴趣的读者可以在著名的hash碰撞工具【hashcat的主页】找到更详细的描述。这篇博文也会使用hashcat作为hash碰撞工具。
好了,至此枯燥的历史和原理就算是讲的差不多了,下面终于可以进入激动人心的实操环节了~

准备工作 [Preparation]

条条大路通罗马,上一小节中提出的破解思路仅仅是原理性的步骤,可以用各种各样的软硬件组合来完成这些操作。不过正所谓工欲善其事,必先利其器,在这里博主对选用的硬件和软件进行一些推荐,推荐的原则主要是三个字:少折腾。希望可以让读者们在尝试的时候少踩些坑。
硬件方面:
1. 无线网卡:推荐使用搭载Intel芯片的无线网卡,而且最好型号不要太新。因为后面我们会用到Linux环境,因此不太推荐使用搭载Realtek芯片的无线网卡。值得注意的是,无线网卡通常有两种形式,一种是通过USB接口连接的,另一种是台式机主板/笔记本自带的。第一种无线网卡可以通过虚拟化软件的USB passthrough功能直接传递给Guest OS,并且可以直接被Guest OS识别为USB无线网卡,所以在进行本文相关的操作时,平时不常用Linux的朋友可以在虚拟机里安装Linux,提升了易用性和容错。第二种无线网卡通过PCIE接口连接至系统,这种是最常见的,但是由于常见的消费级虚拟化软件(例如VMWare Workstation)不支持PCIE设备虚拟化,所以需要在硬件上直接运行Linux操作系统。
2. GPU:非常推荐使用一块16年以后发布的中高端独立GPU进行hash碰撞加速。更详细的来讲,推荐使用算力为6TFLOPS以上的独立GPU,例如Radeon RX 580就是一个很好的选择。AMD公关和市场部门之前的钱还没给我结。在这个【TechPowerUp页面】里可以很方便的搜索到各种GPU所对应的算力。

重要的事情要重复一遍:不推荐使用搭载Realtek芯片的无线网卡。我们后面要使用Linux的环境,而Linux对不少Realtek的无线网卡缺乏支持,或者更确切的说,不少Realtek的无线网卡对Linux缺乏支持。在2020年的今天,绝大多数情况下硬件驱动已经包含在Linux内核中,如果最新的Linux内核中没有某个硬件的驱动程序,要么是硬件提供商没有向Linux提交驱动程序代码,要么是提交了驱动程序代码,结果因为写的实在太烂被暴脾气的Linux创始人Linus Torvalds拒绝合入内核(Realtek就是这种情况)。不管怎么说,A hardware either works with Linux, or it doesn't, 五六年前那种给内核打补丁折腾一下午终于连上Wi-Fi的情况已经不复存在了。除非你非常确定某款特定的Realtek无线网卡具有良好的Linux支持,否则不建议使用Realtek无线网卡进行后续的操作。

软件方面:
1. 四重握手信息抓取环境:推荐使用Kali Linux进行四重握手信息的抓取。Kali Linux由Offensive Security发行和维护,是一种专门用于网络安全评估和渗透攻击的Linux发行版,预装了大量相关的实用工具。Kali Linux基于Debian,这点和Ubuntu一样,所以熟悉Ubuntu系统的读者们上手应该不会有任何难度。不过不太建议将Kali Linux作为日常使用的主力操作系统,就好比不太建议用瑞士军刀来吃饭一样。值得一提的是,如果你采用的无线网卡是上文所说的USB网卡,那你可以在虚拟机环境中安装Kali Linux,并将USB无线网卡passthrough给虚拟机。这样做的好处是既可以方便的在你最熟悉的操作系统环境和Kali Linux之间切换,也不容易因为误操作而把硬盘上原有的系统给搞挂了,在【这个链接】可以下载到最新(2020.02版本)的Kali Linux安装镜像;对于采用PCIE无线网卡的读者则建议通过Live USB的形式启动Kali Linux,好处同样也是不容易破坏原有的操作系统,在【这个链接】可以下载到最新(2020.02)版本的Kali Linux Live USB镜像。
2. hash碰撞环境:推荐使用hashcat进行hash碰撞。在【这个链接】可以下载到最新(6.0.0版本)的hashcat。由于hashcat同时支持Windows和Linux,所以你可以在你最熟悉的操作系统中进行hash碰撞。值得注意的是,AMD GPU + Linux的情况下需要安装RadeonOpenCompute (ROCm),如何安装可以参考【我的这篇博文】中的“安装AMD ROCm平台 [Install AMD ROCm meta-package]”小节;NVIDIA GPU则不论采取什么操作系统都需要安装CUDA Toolkit 9.0以上版本,并确保驱动为最新;AMD GPU + Windows是最省事的组合,只需要确保驱动版本是最新的就行了。我使用的GPU为Radeon RX 5700 XT,为图省事就在Windows环境下进行hash碰撞操作。
3. 一个好用的密码字典:获取一个好用的密码字典其实是一个不那么方便的事情,为了控制一下本文的篇幅,在这里和大家分享一个我自己制作的密码字典,收集了大部分原始大小为200M以下的密码字典,并进行了去重工作。感兴趣的朋友可以尝试自己制作更好的字典。下载密码字典:国内的用户请点击【这个链接】,国外的用户请点击这个【Google Drive链接】。

使用Wifite截取四重握手包 [Intercept 4-Way Handshake with Wifite]

  1. 启动至Kali Linux (或者在虚拟机中启动,并将USB网卡passthrough至虚拟机),并确认你的无线网卡已经正确被系统识别,如下图所示:

  2. 使用命令sudo wifite打开Wifite。Wifite是一个Kali Linux自带的Wi-Fi攻击自动化脚本,集成许多常见的攻击方式,非常方便。打开Wifite之后,脚本首先会将无线网卡置于监听模式(Monitor Mode),之后便会开始搜索并分析周围的Wi-Fi接入点,并将Wi-Fi接入点的名称、认证协议、已经连接的客户端数量和信号强度等关键信息以列表的形式显示出来。列表会以一秒为间隔刷新,当已经搜索到你需要攻击的目标时,按下ctrl + c停止搜索,此时列表也会停止更新,如下图所示(SSID/MAC地址等信息已经隐去):

    在图中,我们可以看到有一些Wi-Fi接入点的认证协议类型为WPA-E,这表明该接入点使用的是企业级的WPA认证协议,此类认证协议目前暂时无法使用Wifite进行四重握手包截取。
  3. 我建立了一个SSID为test的Wi-Fi接入点作为本次实验的攻击对象,并用手机连上了这个接入点。值得注意的是,由于四重握手包截取的原理是需要先将一个合法的客户端“踢”下线,因此这类攻击只适用于已有客户端连接的接入点。如果一个接入点没有客户端接入,那就无人可踢,也就无法截取四重握手了。不过在搜索阶段中,即使某个特定的接入点在列表中显示没有客户端也不需要太过担心,很多时候选中这个接入点进行下一步操作后,慢慢的会搜索到更多的客户端。按下ctrl + c停止搜索后,输入你想要攻击的接入点NUM一栏所对应的数字,并按回车键。也可以用逗号隔开数字的形式选中多个接入点进行攻击。下图中,Wifite开始尝试截取test接入点的四重握手包:

  4. 当Wifite成功截取四重握手包后,会尝试一些最基础的破解,一般来说,只要对方没有使用12345678这种最容易的密码,Wifite自带的hash碰撞能力是无法破解的。不过没关系,在hs目录里可以找到一个.cap文件,这个文件就是刚才截取的四重握手包。至此Wifite的工作就完美结束了,接下来就让更专业的工具来进行hash碰撞。

使用hashcat进行密码破解 [Crack Password with Hashcat]

  1. 使用这个【hashcat的在线工具】将刚才获取的.cap文件转换为.hccapx文件。

  2. 以Windows环境为例,使用以下命令让hashcat使用刚才下载的密码字典来破解.hccapx文件:

    1
    hashcat.exe -m 2500 [hccapx file] [dictionary file] --self-test-disable

    Linux环境下的可执行文件则为hashcat.bin。

  3. 这次分享字典大小算是比较小的,因此可以较快的获得结果,如下图所示:

    可以看到,此次破解共耗时23秒,破解的密码结果为iamironman中二之魂。

  4. 上面的是比较理想的情况,即密码就在字典中,但是很多时候密码可能是字典中的字符串加自定义字符串的形式,这个时候就要用到hashcat的混合模式。下面举一个混合模式的例子:

    1
    hashcat.exe -a 6 -m 2500 [hccapx file] [dictionary file] ?d?d?d --self-test-disable

    其中-a 6表示混合模式,?d?d?d表示末尾添加3个任意数字。hashcat的字符集设定如下所示:

    1
    2
    3
    4
    5
    6
    7
    8
    ?l = abcdefghijklmnopqrstuvwxyz
    ?u = ABCDEFGHIJKLMNOPQRSTUVWXYZ
    ?d = 0123456789
    ?h = 0123456789abcdef
    ?H = 0123456789ABCDEF
    ?s = «space»!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~
    ?a = ?l?u?d?s
    ?b = 0x00 – 0xff
  5. 值得注意的是,在混合模式下,自定义字符串的空间越大,破解时间也就会越长。例如刚才的示例中,以Radeon RX 5700 XT的算力需要运行10小时左右。

最后

根据今天的实验,我们可以发现,随着KRACK攻击的提出、越来越多密码泄露事件的发生和GPU技术的快速发展,即使是使用了WPA2认证协议的Wi-Fi接入点也不再能高枕无忧了。如果Wi-Fi密码就包含在常见的密码字典中,那么这个Wi-Fi接入点的破解可能只需要几分钟的时间;如果Wi-Fi密码为常用词+简单数字的组合,那破解的代价可能也只需要几个小时到十几个小时的时间。
但是也不需要太过担心,遵循以下两条简单的建议即可大大提升Wi-Fi接入点:

  1. 使用较为复杂的密码,可以大大增加破解的代价;
  2. 定期更换密码,可以减小密码出现在密码字典中的概率。

AMD显卡上完美原生运行PyTorch教程,无需容器(Docker)

发表于 2019-06-02 | 分类于 技术 | 评论数: | 阅读次数:
本文字数: 5k | 阅读时长 ≈ 9 分钟

博主的一些废话

本站的【第一篇正经博文】发布之后,受到了各方的好评,在此非常感谢陈老师的【微博转载】,没有陈老师的转发,我的博客是不可能得到那么高的关注度的。陈老师是杜克大学进化智能中心的主任,是我深度学习方向上的启蒙老师,希望大家也能多多关注【他的微博】和【个人主页】,对陈老师研究方向感兴趣的同学也可以考虑报考他的研究生。

非常感谢许多读者对本博客发来的意见和建议,其中出现的最多的两个建议是:
Q1. 使用.tk域名导致分享的链接被微信内置浏览器拦截并重排版,并且重排版以后也无法正确显示。
A1. 现在域名已经改成.net的了,测试了一下不再会被微信内置浏览器拦截。新的博客地址为:https://www.shenjc.net ,旧的域名:https://www.shenjc.tk 依然可以访问,被我URL转发到新的域名了,以后欢迎大家通过新的域名来访问我的博客。
Q2. 博客的功能比较少,没有留言板功能和RSS订阅功能。
A2. 留言板和RSS订阅功能已经上线了。目前留言板还没有开启验证码功能,希望大家不要滥用,请勿发广告。RSS订阅在右下角的黑色小按钮里,或者也可以点击【这个链接】。 再次感谢这些热心读者在没有留言板的状态下,依然通过各种渠道提供了宝贵的反馈意见。
最后,还有一个反馈就是,上一篇博文的废话章节似乎太多了,占据了几乎整篇文章1/3的篇幅,为只想学习如何在AMD显卡上运行PyTorch的读者们带来了一些困扰。以后我会把杂谈类的内容单独分出来,尽量在技术类文章中少讲废话,希望技术类博文能做到干货多,屁话少。

容器真的好难用 [Nothing works in Docker without a struggle]

上一篇博文主要介绍的是如何在容器中运行PyTorch on ROCm,对于直接编译安装则是只给出了大致的思路。限于目前ROCm的开发成熟度,目前在原生环境中编译安装PyTorch需要对本地的ROCm环境进行修改(AMD ROCm software团队承诺在未来版本中会解决这个问题)。这就导致了这篇今天可以正常运行的教程,在未来可能就会过时,这也是我在上一篇博文中没有给出原生环境安装step by step的教程的主要原因。不过,我也没有想到有不少读者也和我一样,对容器有些不太习惯,希望能在原生环境中运行,最终促生了这篇博文。
说实话,容器是一种比较偷懒的办法,最主要的工作是由AMD ROCm software团队帮我们完成了。虽然方便,但是缺点也很明显:

  1. 需要依赖AMD ROCm software团队针对PyTorch的新版本及时发布新的容器镜像,这往往会落后于PyTorch主枝,无法在第一时间享受到PyTorch版本更新所提供的新功能和最新优化。
  2. 同样的,也只能选择镜像中已有的Python版本,无法使用自己用的最顺手的Python版本。
  3. 在AMD ROCm software团队发布的镜像中,只有root用户才能使用PyTorch,导致在容器中运行PyTorch脚本生成的输出文件,文件owner都是root,很容易把项目里的文件权限搞得一塌糊涂。
  4. PyTorch最大的魅力在于可以像普通的Python程序和脚本一样调试,配合PyCharm、Visual Code等工具调试起来非常高效且符合直觉。在容器中部署PyTorch则无法和原生环境中的这些调试环境联动。当然,也可以选择在容器中安装那些东西,不过正如标题所说,Nothing works in Docker without a struggle,既然都要struggle了,那还不如折腾怎么让PyTorch在原生的环境中运行。
  5. 很多读者和我一样,在以前使用容器的过程中有一些不愉快的经验,对于容器的抵触情绪暂时还无法消散。

在这里和做Cloud native、Kubernetes等容器技术的读者说一声抱歉,我真诚的认为容器技术是一项很好的技术,只是目前阶段,在有选择的前提下,容器还不是我的第一选择。

准备阶段 [Preparation]

本文假设你已经参考【上一篇博文】中的教程,正确的配置了github代理加速(建议,可选)和AMD ROCm平台(必须)。

  1. 由于编译需要比较长的时间,建议关闭Ubuntu系统的睡眠功能,以免在编译过程中意外进入睡眠状态。顺便把猫子从桌上赶走。编译到一半被猫子给关了,真是晕倒。

  2. 运行以下命令,确保ROCm的包已经安装完全:

    1
    2
    sudo apt update
    sudo apt install rock-dkms rocm-dev rocm-libs miopen-hip hipsparse hip-thrust rccl
  3. 安装编译所需要的一些工具,编译完成后可以选择删除:

    1
    sudo apt install git python-pip libopenblas-dev cmake libnuma-dev autoconf build-essential ca-certificates curl libgoogle-glog-dev libhiredis-dev libiomp-dev libleveldb-dev liblmdb-dev libopencv-dev libpthread-stubs0-dev libsnappy-dev sudo vim libprotobuf-dev protobuf-compiler
  4. 安装anaconda(可选,建议)。具体步骤请参考【这个链接】。如果安装了anaconda,使用以下命令创建并进入pytorch专用环境:

    1
    2
    conda create --name pytorch python=3.6
    source activate pytorch
  5. 安装PyTorch的Python依赖包:

    1
    pip install enum34 numpy pyyaml setuptools typing cffi future hypothesis

编译安装PyTorch on ROCm [Compile from scratch]

  1. 正如最开始说的,目前ROCm平台还存在一些缺陷,因此编译PyTorch需要修改ROCm平台。修改后的ROCm平台可能不能编译或者运行其他深度学习框架。在root权限下运行以下命令:

    1
    2
    3
    4
    sed -i 's/find_dependency(hip)/find_dependency(HIP)/g' /opt/rocm/rocsparse/lib/cmake/rocsparse/rocsparse-config.cmake
    sed -i 's/find_dependency(hip)/find_dependency(HIP)/g' /opt/rocm/rocfft/lib/cmake/rocfft/rocfft-config.cmake
    sed -i 's/find_dependency(hip)/find_dependency(HIP)/g' /opt/rocm/miopen/lib/cmake/miopen/miopen-config.cmake
    sed -i 's/find_dependency(hip)/find_dependency(HIP)/g' /opt/rocm/rocblas/lib/cmake/rocblas/rocblas-config.cmake

    请各位读者注意,若新版的ROCm平台修复此问题,请忽略该步骤。

  2. 拉取PyTorch的源代码。这里建议拉取AMD ROCm software团队的fork仓库:

    1
    git clone https://github.com/ROCmSoftwarePlatform/pytorch.git

    这个分支会定期Merge来自主支的修改。理论上来说,PyTorch的官方分支也是可以在ROCm平台上编译的,但是我没有试过,不保证可行,如果你希望体验PyTorch最新的功能,可以尝试使用官方仓库:

    1
    git clone https://github.com/pytorch/pytorch.git

    如果碰到什么问题,欢迎各位读者在留言区交流。

  3. 拉取PyTorch的子模块:

    1
    2
    cd pytorch
    git submodule update --init --recursive

    正常情况下,步骤2和步骤3大约需要下载500MB的数据。

  4. 配置你的GPU类型。目前ROCm支持gfx803, gfx900和gfx906这三种GPU。在【上一篇博文】中可以找到各个GPU芯片所对应的类型。如果不能确定,可以通过以下命令获取/opt/rocm/bin/rocm_agent_enumerator。通过设置环境变量,指明编译针对的GPU类型,例如我的RX 580为gfx803,则我的设置为:export PYTORCH_ROCM_ARCH=gfx803

  5. 删除conda环境中的ld(可选,仅针对使用anaconda)。不知道为什么,conda环境中自己有一个ld,会覆盖系统中自带的/usr/bin/ld,会导致编译出问题。通过重命名的方式使其不影响编译:

    1
    2
    3
    cd ~/anaconda3/envs/pytorch/compiler_compat
    mv ld ld-tmp
    cd -

    在编译完成后可以把它改回来。

  6. 使用AMD提供的脚本将PyTorch中的CUDA函数白嫖重用为ROCm中的hip函数。

    1
    python tools/amd_build/build_amd.py

    ROCm的这个'hipify'功能真的很强大,避免了重写海量PyTorch底层CUDA函数的巨大工作量。hipify的存在使得ROCm平台具有在短期内追赶CUDA的潜力。

  7. 编译安装PyTorch:

    1
    2
    export USE_NINJA=1 # 可选
    USE_ROCM=1 USE_LMDB=1 BUILD_CAFFE2_OPS=0 BUILD_TEST=0 USE_OPENCV=1 MAX_JOBS=N python setup.py install

    其中,N为编译时使用的线程数。N越大则编译速度越快,但是要注意的是,请不要把N设的太大,在编译过程中,进程最高会占用5GB的内存,因此请保证5*N小于你的内存总量。否则内存要是超出了可是会死机的。如果因为N设置过大导致编译失败,请使用命令python setup.py clean清空编译环境后再重新编译。

  8. 去休息一下,喝一杯咖啡。编译会比较耗时间。在此期间请不要使用内存占用过高的程序,例如Chrome,避免因为内存不足导致编译失败。

  9. 编译安装torchvision:

    1
    2
    3
    4
    git clone https://github.com/pytorch/vision
    cd vision
    python setup.py install
    cd ..
  10. 运行PyTorch测试。

    1
    PYTORCH_TEST_WITH_ROCM=1 python test/run_test.py --verbose

    可以发现,通过的测试数量比上次Docker安装的更加多了。说明这次的PyTorch安装功能更加完整,理论上能提供更好的开发体验。

  11. Enjoy! 将~/anaconda3/envs/pytorch/bin/python设置为你开发环境的解释器,你甚至感受不到你用的不是CUDA!

最后

希望大家能够多多关注AMD ROCm,我认为这是一个很有潜力的平台。再加上最近AMD的势头不错,会在这个领域继续加大投入,CUDA将会面对激烈的正面竞争。在Reddit上,有用户汇报AMD最新的Radeon VII在Tensorflow benchmark上超过了同价位的RTX 2080大约10%至20%,FP16和FP32都是如此。Radeon VII在没有专门针对FP16设计Tensorcore的不利条件下依然力克同价位的竞品,再加上用料十足的16GB HBM2显存,足以体现AMD在产品方面的诚意。目前挡在Radeon Technology Group和深度学习之间的,可能只有部署难度了。希望这篇博客可以帮助到各位,麻烦AMD公关部门看到这篇文章,主动结一下广告费,谢谢(Just kidding)。

AMD显卡上使用容器运行PyTorch免踩坑教程

发表于 2019-05-28 | 分类于 技术 | 评论数: | 阅读次数:
本文字数: 9.6k | 阅读时长 ≈ 17 分钟

博主的一些废话

作为这个博客第一篇正经的文章,没点干货怕是镇不住场子。去年年底来了一波矿难,我也就趁显卡价格崩盘之前,卖光了我全部的显卡,然后以500多块的价格搞了一个二手的RX 580 4GB。不得不说这个价格真的是香。再加上近两年来,基于面向风口编程的思想,自学了一些深度学习的知识,所以不得不好奇目前AMD GPU在深度学习框架加速上到底搞得怎么样了。不搜不知道,一搜吓一跳,目前关于AMD GPU for deep learning的讨论非常匮乏,尤其是针对PyTorch的讨论,英文的内容(包括官方文档)都有些过时,中文的讨论更是完全没有。本文很荣幸的能成为了也许是全网首发中文版PyTorch on AMD GPU的攻略。本篇博文主要介绍如何在容器(Docker)环境中部署PyTorch on ROCm,更希望在原生Ubuntu环境中部署的朋友可以参考我的【这篇博文】。

趁着今天AMD在台北的风光表现,想先聊聊AMD是如何在两年时间内180度扭转我对它的看法的。虽然早在2016年就听到过AMD ROCm的风声,但是说实话那个阶段的我还是有点看不起AMD的。彼时的AMD,CPU方面,zen架构吹了两年,但是还看不见影子;GPU方面,Raja大神年年rebrand上瘾,迟迟拿不出能和Maxwell正面竞争的产品,更何况下半年大杀特杀的Pascal。吹了半天的Polaris也就靠着制程优势才勉强达到GM204的水平。

直到后来的2017年,Dr. Su祭出大杀器Ryzen系列CPU,我对AMD的看法才算有点改观。后面的故事大家应该比较熟悉了,2018年的二代Ryzen补全了一代一些兼容性和内存控制器性能方面的问题,得益于多线程性能和价格优势,得到了市场良好的反应。而老对手Intel这段时间里可谓是多灾多难,超线程相关的安全性问题出了一个又一个,每次补丁都降一点性能。今天,Dr. Su又风光满满的宣布了zen2架构的三代Ryzen CPU,首发7nm桌面处理器,IPC+15%, 2$\times$浮点性能和2$\times$缓存容量;同时隔壁蓝队14nm工艺+了又+,谁来和我说一下最新的14nm工艺后面有几个加号了,桌面级的10nm处理器要到2020年末才能上线。至此AMD的CPU产品线算是对Intel完成了全面的超越,包括游戏、工作站、笔记本和服务器四大平台。

这段时间里AMD的GPU尽管产品线依然走的很艰难,但是有一个重要的变化不可忽视:大神Raja终于转投Intel,不再祸害AMD,把Radeon部门搞得全是咖喱味了,这段话会变得非常政治不正确,please bear with me。我对Raja大神的负面看法偏见也是有依据的:Raja在AMD的这段时间内,做出最大的贡献就是把显卡部门变得更加独立了,产品力方面则是一年比一年不给力,和老对手NVIDIA的差距从六四开变成了九一开,不得不让人联想到“硅谷里的印度人只会吹牛皮和搞办公室政治”这个传言。当然啦,Raja大神去了Intel之后是风采依旧,下面给大家欣赏一下Raja大神最近5月10日的投资人会议上放出的牛逼闪闪的一张幻灯片:

我就问你484牛逼闪闪,代表Intel的蓝色圆那么大,代表NVIDIA的绿色圆那么小,代表他老东家AMD的红色圆更加小。Raja大神在圆下面写了"Competitor 1"和"Competitor 2",算是给红绿两家留了点最后的面子。你要是投资人,看了这个幻灯片,是不是觉得卧槽Intel这也太牛逼了,还不赶紧投他一个亿。如果你是这么想的,那么恭喜你也被Raja大神忽悠瘸了。在Raja大神发完这张幻灯片的下一个交易日5月13日,Intel股价相比上一个交易日上涨-3.1%。个人觉得Raja大神非常需要看一下本博客的上一篇博文,学习一下什么叫做FSS。

不好意思,一黑起Raja大神我就有点刹不住车了,这段一不留神写的那么长了,最后再声明一下,对于Raja大神的这些看法都不是我瞎编的,AMD的subreddit里也不乏类似的讨论,比如【这个帖子】(International internet connection required)。

好了,废话到此结束,开始放干货~

为什么要用AMD GPU [Why AMD GPU]

和写好一篇论文一样,写好一篇教程,首先是立意要立的高高的!

使用AMD GPU做深度学习加速的优势主要有以下四点😎:

  • AMD提供更高的性价比,闻到了贫穷的气息。作为长期的underdog,AMD的GPU在第三方游戏优化和能耗比上都远远落后于NVIDIA,因此只能通过提供更高的性价比来保持竞争力。相信有过挖矿经验的朋友都会发现,同等定位的A卡往往比N卡具有更强的挖矿能力,这是因为售价接近的前提下,A卡具有更高的理论计算能力,如下表所示:

    显卡型号 GPU芯片型号 FP32性能 售价(MSRP) 性价比
    RX 480 8GB Ellesmere 5834 GFLOPS $239 24.41 GFLOPS/$
    GTX 1060 6GB GP106 4375 GFLOPS $249 17.57 GFLOPS/$

    另外,现在是矿难之后,A卡二手售价白捡的一样,正是业余爱好者低成本入坑的大好机会。有人会说矿卡分分钟翻车。其实根据我多年挖矿的经验,维护良好的矿卡根本没有那么容易翻车,至少我个人挖了两年多的矿,几十块矿卡,型号包括AMD这边的RX 480、RX 470和NV这边的GTX 1060,没有一块显卡是因为挖矿挖挂了的。我对矿卡的信心主要来源于三点:1. 挖矿对显卡的稳定性要求比日常使用高多了,毕竟一块显卡跑飞了,重置驱动就要牵连这个系统上的其他所有显卡,这么重置一番损失的可都是利润,所以反而是稳定性比较高的显卡才能长期胜任挖矿任务;2. 从16年开始兴起的这一波显卡挖矿潮主要挖的是以太坊,这类挖矿算法对于核心频率并不敏感,所以挖矿的时候为了减少耗电,往往会降低核心的电压和频率,对于核心芯片的压力并不大;同时这类挖矿算法对显存延时非常敏感,而像游戏玩家那样把显存频率超的很高的话时序会变差,延时反而会增加,所以显存芯片的压力也并不是很大;3. 挖矿的显卡都是放在开放式的机架上,住在常年20度的空调房里,运行环境比闷在机箱里的游戏显卡好多了。 当然,以上的三点只针对“维护良好”的矿卡,在购买矿卡之前建议学习一些基本的测试和鉴别能力。

  • AMD的ROCm(Radeon Open Compute)是一个开放性非常强的项目。ROCm对标的对手是NVIDIA的CUDA(Compute Unified Device Architecture)。说起来CUDA其实是一个很有前瞻性的项目,旨在为NVIDIA的GPU提供高抽象层级的通用计算支持。最初发布于2007年,几乎每年都会迭代新版本,使得其GPU占据了全部的通用计算市场。在CUDA发布之初,GPU通用计算市场其实也就是蚊子腿那么大一块,掌管AMD显卡部门的Raja大神自然不会把它放在眼里。然而时间一晃到了2014年,深度学习正式起飞,GPU通用计算市场爆发式增长,AMD才算缓过神来,于是在2016年也推出了NVIDIA CUDA的替代品ROCm。相比于CUDA,ROCm拥有比更强的包容性和开放性,下面这张摘自AMD ROCm initiative的图片很好的诠释了ROCm的野心,从图中可以看出,ROCm和CUDA最大的区别在于其开放性:和CUDA只能在特定型号的NVIDIA GPU上运行不同,ROCm希望能在各种不同的硬件上运行。可惜现在还不行。

  • 更重要的一点是,ROCm是完全开源的(【源代码】),这在贸易战背景下的今天显得格外重要。CUDA是完全闭源的软件,虽然可以免费获取,免费使用其二进制包,但是NVIDIA拥有随时修改其授权方式的权力。这可不是危言耸听,NVIDIA可是有过此类先例的:2017年的圣诞节,NVIDIA修改了其GeForce驱动程序的EULA,规定其不得在数据中心部署(链接)。在未来,NVIDIA很有可能会迫于白宫的政治压力,Trump: Give Chaina CUDA, not good!,从而限制CUDA在中国地区的部署。相比之下,ROCm平台中的项目多采取MIT License或者其变种,通俗的来说就是最宽松的开源协议,只要求使用者给他署一个名就行。除此之外,ROCm谐音Rock'em,是不是很帅!比起遵循元辅音结构本来应该念/kju:də/但是却偏偏念成/ku:də/的CUDA强到不知道哪里去了。

  • 最后,Because we can! Because I'm Batman!!! 生命的意义在于折腾,不折腾浑身难受。

为什么要用PyTorch [Why PyTorch]

自从2017年以来,赞美PyTorch的文章已经不计其数了,所以在这里就不再赘述了。其实最主要的原因还是TensorFlow真的太难用,于是又出现了针对TensorFlow开发各种各样的Wrapper,比如有Keras,TFLearn,TensorLayer等。甚至Google自己人都觉得这个货太不好用,Google Deepmind为此也开发了自己的wrapper,叫做sonnet。TensorFlow本身就是其后端引擎的Python wrapper,然后在wrapper上面再包wrapper,这种搞法真的让我有些接受不了。

相比之下,PyTorch就简洁明了的多,动态的计算图的设计使其非常容易Debug,而且PyTorch的底层的代码更是干净的多,即使真的有自己修改底层引擎源代码的需求,改起来也会较为方便,不像TensorFlow有种“look but don't touch”的感觉。此外,AMD官方团队对于TensorFlow on ROCm的支持开发力度是所有深度学习框架中最完善的,因此如果你希望使用ROCm加速TensorFlow,仔细跟随官方文档的步骤应该就不会有太多的问题,相关的中文文档也能找到不少。而对于PyTorch的支持就比较有限,官方的文档有些过时,并且中文的相应资料几乎没有,给了我折腾的空间。

准备步骤 [Preparation]

本文假设你已经具有以下能力:

  • 基本的bash shell使用。
  • 安装Ubuntu操作系统。

准备步骤要完成的有:

0. 确保你的显卡支持ROCm。目前ROCm仅官方支持2015年以后新发布的GPU芯片。注意这里是按芯片的发布时间,而不是显卡发布的时间。例如2015年发布的R9 390X显卡,其GPU芯片是2013年发布的Hawaii,因此依然不能获得ROCm完整的官方支持。
目前官方支持的GPU芯片型号有:

  • GFX8:
    • Fiji家族,例如R9 Fury和R9 Fury X。
    • Polaris家族,例如RX 480、RX 470和RX 580等。
  • GFX9:
    • Vega 10家族,例如RX Vega 56和RX Vega 64。
    • Vega 20,例如Radeon VII。

另外,Hawaii家族的芯片可以运行ROCm平台,但是不保证功能的完全支持。Hawaii家族的显卡有R9 290,R9 390X等。

1. 安装Ubuntu操作系统。在这里推荐安装Ubuntu 16.04 LTS版本,毕竟这是官方支持的版本,出现奇怪问题的概率会小一点。不过也很推荐Ubuntu 18.04 LTS版本,作为最新的LTS版本,支持时间可以久一点,而且1804版的默认stock gnome桌面环境真的很棒,比unity高到不知道哪里去了。安装完系统之后,首先更新,安装libnuma,再重启一下:

1
2
3
4
5
sudo apt update
sudo apt upgrade -y
sudo apt install libnuma-dev -y
sudo apt autoremove -y
sudo reboot

2.使用Shadowsocks加速网络连接。作为一个future proof向的攻略,本文考虑到了人类移民火星之后,火星用户访问地球互联网速度较慢的情况,地球用户可以直接跳过步骤2、3和5。本文假设你已经有了一个连接地球网速较快的shadowsocks服务器。

  • 首先使系统连接Shadowsocks服务器。这里建议使用shadowsocks-qt5客户端,点击【这里】下载shadowsocks-qt5可执行文件。
  • 将下载完成的文件放到你喜欢的位置,然后在这个目录打开终端,给文件添加可执行权限:
    chmod a+x Shadowsocks-Qt5-3.0.1-x86_64.AppImage
  • 启动shadowsocks-qt5客户端:
    ./Shadowsocks-Qt5-3.0.1-x86_64.AppImage
  • 在下图所示的客户端图形配置界面里面填写正确的服务器信息,注意本地代理类型推荐使用socks5。完成后点击连接按钮。

3. 安装tsocks。使用以下命令安装tsocks:sudo apt install tsocks -y. 修改/etc/tsocks.conf文件使得最后三行设置和你shadowsocks-qt5中本地代理的设置相同,默认情况应该是这样:

1
2
3
server = 127.0.0.1
server_type = 5
server_port = 1080

4. 安装git。Ubuntu默认是没有git的,所以使用以下命令安装git:sudo apt install git -y.

5. 配置git使其通过shadowsocks加速。通过以下配置,让git走shadowsocks的socks5代理:

1
2
git config --global http.proxy 'socks5://127.0.0.1:1080' 
git config --global https.proxy 'socks5://127.0.0.1:1080'

6. WARNING:千万不要花时间去customize你的Ubuntu操作系统。这是一件很过瘾但是同时又非常花时间的事情,就像爱丽丝的兔子洞一样,你一旦钻进去了就很难钻出来了,不要问我怎么知道的,匡扶汉室都给耽误了。

安装AMD ROCm平台 [Install AMD ROCm meta-package]

由于AMD ROCm包含了单独的驱动,而且和普通的游戏驱动不能共存,所以首先确保系统中不存在AMD GPU-PRO版显卡驱动。如果有,使用以下命令删除驱动并重新启动:

1
2
3
sudo amdgpu-pro-uninstall
sudo apt autoremove -y
sudo reboot

1. 添加ROCm的apt仓库。使用以下命令将ROCm的官方apt仓库添加到你的apt源中:

1
wget -qO - http://repo.radeon.com/rocm/apt/debian/rocm.gpg.key | sudo apt-key add - echo 'deb [arch=amd64] http://repo.radeon.com/rocm/apt/debian/ xenial main' | sudo tee /etc/apt/sources.list.d/rocm.list

2. 通过apt安装rocm-dkms包。使用以下命令安装rocm-dkms包:

1
2
sudo apt update
sudo apt install rocm-dkms

不出意外的话,这次安装大约要下载400MB的数据。如果你发现你的下载速度特别慢,很有可能你用的是火星网络,这时候就可以用到上面安装和配置的tsocks来加速apt。使用以下命令来代替刚才的命令:

1
2
sudo tsocks apt update
sudo tsocks apt install rocm-dkms

如果你的加速节点连接地球网络比较通畅,那你会发现下载时间从半天变成了十几分钟,爽到😏!

3. 为你的用户添加GPU访问权。使用以下命令将你的用户添加到可以访问GPU的用户组中:

1
sudo usermod -a -G video $LOGNAME

使用以下命令可以让你以后为系统新添加的用户都有GPU访问权:

1
2
echo 'ADD_EXTRA_GROUPS=1' | sudo tee -a /etc/adduser.conf
echo 'EXTRA_GROUPS=video' | sudo tee -a /etc/adduser.conf

4. 将ROCm添加到PATH环境变量中。使用以下指令可以对全部用户生效:

1
echo 'export PATH=$PATH:/opt/rocm/bin:/opt/rocm/profiler/bin:/opt/rocm/opencl/bin/x86_64' | sudo tee -a /etc/profile.d/rocm.sh

至此,ROCm平台安装完成。重新启动以后,使用以下命令检查是否正确安装:

1
2
/opt/rocm/bin/rocminfo 
/opt/rocm/opencl/bin/x86_64/clinfo

如果正确安装,你的显卡设备应该显示在Agent列表中,例如我的RX 580在rocminfo中的显示结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
*******                  
Agent 2
*******
Name: gfx803
Vendor Name: AMD
Feature: KERNEL_DISPATCH
Profile: BASE_PROFILE
Float Round Mode: NEAR
Max Queue Number: 128
Queue Min Size: 4096
Queue Max Size: 131072
Queue Type: MULTI
Node: 1
Device Type: GPU

在clinfo中显示结果如下:

1
2
3
4
5
Number of devices:				 1
Device Type: CL_DEVICE_TYPE_GPU
Vendor ID: 1002h
Board name: Ellesmere [Radeon RX 470/480/570/570X/580/580X]
Device Topology: PCI[ B#1, D#0, F#0 ]

可选。安装radeontop来监测GPU使用情况。说起来AMD的软件支持还真的是有点腿,驱动连nvidia-smi类似的功能都没有。目前只能用第三方开源软件来实现类似的功能:

1
2
sudo apt install radeontop
sudo radeontop

感觉这个radeontop也挺不好用的,显存占用也看不到。

使用Docker安装ROCm版的PyTorch [Install PyTorch on ROCm in a Docker]

0. 建议采用docker部署镜像的方式安装。首先使用以下命令安装docker:

1
sudo apt install docker.io

1. 拉取docker。这里官方的文档中仅提供了GFX900的镜像,而且里面的Python版本是很过分的2.7版。个人建议拉取以下镜像:

1
docker pull rocm/pytorch:rocm2.3_ubuntu16.04_py3.6_pytorch

这个镜像中Python版本是还行但任然不是最新的3.6版。
拉取镜像大约要下载4GB的数据,不过由于Docker Hub在火星也有CDN,因此大部分用户应该都能获得还过得去的下载速度。

2. 启动docker。使用以下命令启动并进入Docker:

1
sudo docker run -it -v $HOME:/data --privileged --rm --device=/dev/kfd --device=/dev/dri --group-add video rocm/pytorch:rocm2.3_ubuntu16.04_py3.6_pytorch

其中-v $HOME:/data命令使得你的home目录被映射到docker中的/data目录。

3. 运行PyTorch测试。 在Docker中使用以下命令运行PyTorch自带的测试:

1
2
cd /pytorch
PYTORCH_TEST_WITH_ROCM=1 python test/run_test.py --verbose

大部分测试应该都能通过。由于ROCm的PyTorch并没有完全在每种GPU上支持PyTorch的全部CUDA函数,小部分很有可能通过不了。

性能测试 [Performance test]

至此终于算是大功告成,成功安装了PyTorch on ROCm。我还在自己的设备上运行了简单的视觉类测试。

CIFAR数据集上的性能。我这里有一份自己写的CIFAR数据集上训练VGG-16网络的代码,可以通过以下命令获取:

1
git clone https://github.com/JC-S/TensorClog_Public

注意在运行之前,首先将utils.py文件中的torch.backends.cudnn.benchmark = True注释掉。这个flag用于提高Pascal及更新的NVIDIA GPU的性能,在ROCm环境下可能会导致死循环。注释完成之后,使用python pretrain.py命令训练VGG-16网络。

在我的测试中,RX 580 4GB,核心频率1380 MHz,显存频率1750 MHz,运行一个epoch训练大约耗时33秒。作为比较,GTX 1080 Ti运行一个epoch大概为16秒。目测RX 580的PyTorch训练性能和GTX 1070或者GTX 1070 Ti持平,按3D渲染性能来比较的话妥妥的算是越级打怪了。

最后

AMD的CPU在最近三年内给我们上演了underdog takeover的大戏,作为一个吃瓜群众我看的还是挺过瘾的。希望这篇博文能帮到使用AMD GPU又对Deep learning感兴趣的朋友。顺便,还希望Raja大神不要辜负我的期望,在Intel能继续稳定发挥,这样我若干年以后也可以拿这篇博文来对某不信邪的光头说一句‘Told you’😎。

FSS是个啥玩意

发表于 2019-05-27 | 分类于 其他 | 评论数: | 阅读次数:
本文字数: 1.2k | 阅读时长 ≈ 2 分钟

博主的一些废话

我一直有一个不大不小的毛病,就是有时候喜欢瞎想,特别是夜里睡不着的时候。于是在某一次瞎想中,我就在想如何把这个毛病,变成一个多少也有点好处的事情。最近写技术博客是越来越流行了,加上写作一直是我的一大弱点,本着熟能生巧的精神,就萌生了何不自己建一个博客的念头。但是,作为一个比较难搞的人,自然是不会希望把自己的博客放到新浪、CSDN这些地方,同时又一直嫌wordpress太难用,这个人真的是有够难搞,所以这个念头就一直搁着了。

前段时间在帮朋友忙的时候接触了一些node.js和前端的知识,发现了hexo这个有趣的东西,最近又突然空了下来,于是心动变成了行动,经过一段时间的折腾之后这个博客终于算是上线了。这个博客最开始的几篇文章已经想好了主题,希望能介绍一些Deep Leaning相关的偏门知识,以及对技术相关消息的一些吐槽。最后希望自己能够持之以恒,把这个博客坚持写下去。

道理我都懂,所以FSS到底是个什么东西

FSS = False Sense of Security,意为虚假的安全感。人们常常会由于无知而对事物产生错误的估计;此时,如果对自己的无知没有自知,就会对自己错误的估计产生很强的信心。

错误的估计+很强的信心是一个非常危险的组合,举一个比较有名的例子,在著名科幻小说《三体》中,太阳系人类在坐标暴露之后,为了躲避即将到来的黑暗森林打击,提出了掩体计划,将聚居地转移到大型气态行星的背面以对抗能引爆太阳的光粒武器。然而未曾想到,到来的黑暗森林打击使用的武器是太阳系人类从未设想过的二向箔,整个太阳系被降至二维,掩体计划成为了徒劳,太阳系文明最终灭绝。作者刘慈欣对于掩体计划最终的评价是“弱小和无知不是生存障碍,傲慢才是”。《三体》中的掩体计划其实就是人类整体的虚假安全感,最终造成的代价是巨大的。同样的例子还有《权力的游戏》中,“红毒蛇”奥伯伦亲王在与“魔山”的决战中,在几乎确定胜局的巨大优势下没有给予对手致命一击,最终被对手抓住机会,导致了自己的惨死。

虚假安全感之所以频繁出现在各类文学和影视作品中,是因为这是人类的一种通病。多年的IT运维经验虽然玩的都是野路子让我认识到,安全是没有绝对的。再坚固的城堡,也只有在其被攻破之前才是坚不可摧的。博客之所以取这个名字,是想要提醒自己要避免产生虚假安全感。要做到这一点其实也不难,虚假安全感其实就是无知+不自知的组合。在我全部知晓的知识中,我只有对这一点是深信不疑的:无知是人类的常态。由于知识是延拓的,了解的知识越多,就越能看清自己还不了解的知识。既然无法摒弃无知,那就只能加强自知,希望这个标题能时刻提醒我,to be a modest man with much to be modest about.

最后

FSS其实还有一些其他的有趣含义,其中我最喜欢的一个是For S***'s Sake。人生免不了会有不愉快的surprise,这时候说一句FSS我想也无妨吧~

JC-S

4 日志
2 分类
10 标签
RSS
© 2021 未经允许请勿转载。All rights reserved by @JC-S.