<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Monsoon's Blog</title><link>https://monsoon-cs.moe/zh/</link><description>Recent content on Monsoon's Blog</description><generator>Hugo</generator><language>zh-CN</language><lastBuildDate>Sat, 12 Oct 2024 00:00:00 +0000</lastBuildDate><atom:link href="https://monsoon-cs.moe/zh/index.xml" rel="self" type="application/rss+xml"/><item><title>关于 IPv6 地址分配的一切</title><link>https://monsoon-cs.moe/zh/2024-10-12-all-about-ipv6-addr-alloc/</link><pubDate>Sat, 12 Oct 2024 00:00:00 +0000</pubDate><guid>https://monsoon-cs.moe/zh/2024-10-12-all-about-ipv6-addr-alloc/</guid><description>&lt;h2 id="序言"&gt;序言&lt;/h2&gt;
&lt;p&gt;IPv4 只有一种动态地址分配方式，即 DHCP，但 IPv6 就有 SLAAC 和 DHCPv6 两种分配方式，同时 DHCPv6 还存在 PD (Prefix Delegation) 的扩展。这三种分配方式之间又存在交互，使得 IPv6 分配过程中出现的问题远比 IPv4 多。大多数可以搜到的教程只从表面解决了问题，对于其后的技术细节模棱两可，而没有从根本上厘清 IPv6 与 IPv4 的差异，&lt;/p&gt;</description><content:encoded><![CDATA[<h2 id="序言">序言</h2>
<p>IPv4 只有一种动态地址分配方式，即 DHCP，但 IPv6 就有 SLAAC 和 DHCPv6 两种分配方式，同时 DHCPv6 还存在 PD (Prefix Delegation) 的扩展。这三种分配方式之间又存在交互，使得 IPv6 分配过程中出现的问题远比 IPv4 多。大多数可以搜到的教程只从表面解决了问题，对于其后的技术细节模棱两可，而没有从根本上厘清 IPv6 与 IPv4 的差异，</p>
<p>此文旨在从相关基础概念出发，授人以渔地讲清楚 IPv6 三种地址分配方式的工作原理，帮助彻底解决 IPv6 分配中的疑难杂症。</p>
<h2 id="ipv6-基础概念">IPv6 基础概念</h2>
<h3 id="lla-link-local-address链路本地地址-和-eui-64">LLA (Link-Local Address，链路本地地址) 和 EUI-64</h3>
<p>LLA 其实在 IPv4 中就已存在，当 DHCP 没有正常工作时，一些操作系统就会为网络接口分配一个 <code>169.254.0.0/16</code> 的地址，用于临时的点对点通信。但 LLA 在 IPv4 中并不重要，只扮演一个可有可无的备用角色，只有当 DHCP 故障时才会出现，因而绝大部分人（包括笔者）直到 IPv6 普及时才了解到 LLA 的存在。</p>
<p>IPv6 LLA (<code>fe80::/8</code>) 继承了 IPv4 LLA 点对点通信的基本功能，但更进一步承担了 NDP (Neighbor Discovery Protocol，邻居发现协议) 以及 SLAAC (Stateless Address Autoconfiguration，无状态地址自动配置) 的重要功能。理解它才能理解 SLAAC 的工作原理。</p>
<p>举例来说，当两个网口通过网线直接相连后，就会分别自动生成 IPv6 LLA，如 <code>fe80::dfc2:d2aa:c86f:171e/64</code> 和 <code>fe80::da8f:9d5b:57e3:c6a6/64</code>，两者都可以 <code>ping</code> 通对方的 LLA。在 Linux 上通过 <code>ip -6 route</code> 命令，可以查到自动配置的 LLA 路由项：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-txt" data-lang="txt"><span class="line"><span class="cl">fe80::/64 dev eth0 proto kernel metric 1024 pref medium
</span></span></code></pre></td></tr></table>
</div>
</div><p>IPv6 LLA 使用特定的算法从 MAC 地址中生成，即 EUI-64，例如网口的 MAC 地址为 <code>70:07:12:34:56:78</code> 时，生成的 EUI-64 为 <code>7207:12ff:fe34:5678</code>，LLA 则为 <code>fe80:7207:12ff:fe34:5678/64</code>（EUI-64 加上 <code>fe80</code> 的前缀）。具体的生成方式下图所示：</p>
<p><img alt="IPv6 LLA 生成过程，图源 https://www.networkacademy.io/ccna/ipv6/stateless-address-autoconfiguration-slaac" loading="lazy" src="/2024-10-12-all-about-ipv6-addr-alloc/generating-link-local-address-example.png"></p>
<p>一般而言，路由器不会转发 LLA 地址的流量，它<strong>仅用于链路点对点通信</strong>。</p>
<h3 id="gua-global-unicast-address全局单播地址">GUA (Global Unicast Address，全局单播地址)</h3>
<p>IPv6 GUA (<code>2000::/3</code>) 可以对应到 IPv4 “公网 IP”的概念。理论上它是全球唯一的，并且可以用于公网通信。一个配置良好的网络架构应当能使每个设备都获取到 IPv6 GUA，以最大程度上发挥 IPv6 的 P2P 通信优势。</p>
<h3 id="私有地址">私有地址</h3>
<p><code>fc00::/7</code> 被定义为 IPv6 的私有地址，类似于 IPv4 中 的 <code>10.0.0.0/8</code>、<code>172.16.0.0/12</code> 和 <code>192.168.0.0/16</code>，用于局域网通信。与 LLA 不同的是，它可以被路由器转发。</p>
<p>由于 IPv6 被设计为全球每个设备都能分到 GUA，私有地址在 IPv6 中的作用被大大削弱。当无法做到为每个设备分配 GUA 时（如一些校园网环境），在内网分配 IPv6 私有地址可以作为替代方案，让内网设备可以访问 IPv6。</p>
<h3 id="组播-multicast">组播 (Multicast)</h3>
<p>IPv6 组播地址（<code>ff00::/8</code>）与 IPv4 组播地址（<code>224.0.0.0/4</code>）类似，用于网段内的一对多通信。<strong>SLAAC 和 DHCPv6 都依赖组播工作</strong>。常用的组播地址有：</p>
<ul>
<li><code>ff02::1</code>：本地链路所有节点；</li>
<li><code>ff02::2</code>：本地链路所有路由器。</li>
</ul>
<h3 id="ndp-neighbor-discovery-protocol邻居发现协议">NDP (Neighbor Discovery Protocol，邻居发现协议)</h3>
<p>NDP 工作于 ICMPv6 之上，类似于 IPv4 ARP，用于发现数据链路层中其他节点和相应的 IPv6 地址，并确定可用路由和维护关于可用路径和其他活动节点的信息可达性。<strong>SLAAC 基于 NDP 工作</strong>，涉及的报文类型有：</p>
<ol>
<li>RS（Router Solicitation）和 RA（Router Advertisement）：用于配置 IPv6 地址及路由；</li>
<li>NS（Neighbor Solicitation）和 NA（Neighbor Advertisement）：用于查找链路上其他设备的 MAC 地址。</li>
</ol>
<h2 id="slaac-stateless-address-autoconfiguration-无状态地址自动配置">SLAAC (Stateless Address Autoconfiguration, 无状态地址自动配置)</h2>
<p>SLAAC 是 <a href="https://datatracker.ietf.org/doc/html/rfc4862">RFC 4862</a> 中定义的 IPv6 地址分配方式，也是<strong>推荐的分配方式</strong>。事实上 Android 只支持 SLAAC IPv6 分配。</p>
<p>SLAAC 最大的特点就是无状态（stateless），即不需要一个中心化的服务器来负责分配。下面笔者用一个例子说面 SLAAC 的过程。</p>
<p>假设<strong>路由器</strong>上的 <code>lan0</code> 网口和<strong>主机</strong>上的 <code>eth0</code> 网口相连，<code>lan0</code> 的 LLA 是 <code>fe80::1/64</code>，<code>eth0</code> 的 MAC 地址为 <code>70:07:12:34:56:78</code>。同时，路由器持有 <code>2001:db8::/64</code> 的 GUA 前缀，即这个子网下所有 GUA 都会被上级路由器路由到此路由器的 <code>wan</code> 网口。SLAAC 的流程如下：</p>
<ol>
<li>
<p><code>eth0</code> 根据 MAC 地址生成 EUI-64 <code>7207:12ff:fe34:5678</code> 和 LLA <code>fe80:7207:12ff:fe34:5678/64</code>；</p>
</li>
<li>
<p>主机执行 DAD（Duplicated Address Detection）确保 LLA 在本地链路中唯一。其和地址分配无关，因而在此略过，有兴趣的读者可以自行查阅相关资料；</p>
</li>
<li>
<p>主机通过 <code>eth0</code> LLA 发送 RS 消息。RS 使用组播地址 <code>ff02::2</code> 发送给本地链路所有的路由器。</p>
</li>
<li>
<p>路由器回复 RA 消息给 <code>eth0</code> LLA。RA 中包含前缀 <code>2001:db8::/64</code>、有效期和 MTU 等信息。</p>
</li>
<li>
<p>主机收到 RA，将前缀和 EUI-64 组合成 <code>2001:db8::7207:12ff:fe34:5678/64</code> 分配给 <code>eth0</code>，并添加路由表：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-txt" data-lang="txt"><span class="line"><span class="cl">2001:db8::/64 dev eth0 proto ra metric 1024 expires 2591993sec pref medium
</span></span><span class="line"><span class="cl">default via fe80::1 dev eth0 proto static metric 1024 onlink pref medium
</span></span></code></pre></td></tr></table>
</div>
</div></li>
<li>
<p>主机进行 DAD 检测，并使用 NA 消息向链路上的邻居通告新地址的使用。</p>
</li>
</ol>
<p><img alt="SLAAC 流程，图源 https://www.networkacademy.io/ccna/ipv6/stateless-address-autoconfiguration-slaac" loading="lazy" src="/2024-10-12-all-about-ipv6-addr-alloc/ipv6-stateless-address-autoconfiguration.gif"></p>
<p>SLAAC 看起来很美好，但有个<strong>重要缺陷</strong>：不支持 DNS 信息的下发，主机必须通过其他方式（通常是 DHCPv6）获取 DNS。RA 中有两个标志位用以解决此问题：</p>
<ul>
<li><code>M</code> (Managed Address Configuration)：可以通过 DHCPv6 获取地址信息；</li>
<li><code>O</code> (Other Configuration)：可以通过 DHCPv6 获取其他信息（如 DNS）。</li>
</ul>
<p>而更新的 <a href="https://datatracker.ietf.org/doc/html/rfc8106">RFC 6106</a> 则通过在 RA 中添加 RDNSS（Recursive DNS Server）和 DNSSL（DNS Search List），支持了 DNS 信息的下发。各操作系统对于 RDNSS 的支持度见 <a href="https://en.wikipedia.org/wiki/Comparison_of_IPv6_support_in_operating_systems">Comparison of IPv6 support in operating systems</a>。在实际使用中，绝大部分情况下只需要配置 IPv4 DNS（通过 DHCPv4 获得），因而 RDNSS 扩展的意义并不大。</p>
<p>以上基于 EUI-64 的 SLAAC 地址配置存在的问题是，<strong>它生成的地址是固定并且可预测的</strong>，这会带来安全性和隐私问题。<a href="https://datatracker.ietf.org/doc/html/rfc4941">RFC 4941</a> 定义的 IPv6 SLAAC 隐私扩展解决了这一问题。它在 SLAAC 时同时生成随机的、定期更换的地址，以解决隐私问题。同时 EUI-64 生成的地址也被保留，用于外部传入连接。在启用隐私扩展的情况下，Linux 中生成的 IPv6 地址例如（从上到下分别是隐私地址、EUI-64 GUA、LLA）：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-txt" data-lang="txt"><span class="line"><span class="cl">2: eth0: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 1500 qdisc cake state UP group default qlen 1000
</span></span><span class="line"><span class="cl">    link/ether 70:07:12:34:56:78 brd ff:ff:ff:ff:ff:ff
</span></span><span class="line"><span class="cl">    inet6 2001:db8::dead:beef:aaaa:bbbb/64 scope global temporary dynamic
</span></span><span class="line"><span class="cl">       valid_lft 2591998sec preferred_lft 604798sec
</span></span><span class="line"><span class="cl">    inet6 2001:db8::7207:12ff:fe34:5678/64 scope global dynamic mngtmpaddr noprefixroute
</span></span><span class="line"><span class="cl">       valid_lft 2591998sec preferred_lft 604798sec
</span></span><span class="line"><span class="cl">    inet6 fe80:7207:12ff:fe34:5678/64 scope link
</span></span><span class="line"><span class="cl">       valid_lft forever preferred_lft forever
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="dhcpv6">DHCPv6</h2>
<p>DHCPv6 和 DHCPv4 的运行方式整体相同，主机发送 <code>ff02::1:2</code> UDP 端口 547 的组播消息，DHCPv6 server 回复地址和 DNS 等信息。</p>
<p>有所不同的是，DHCPv6 可以在有状态或者无状态的模式下运行，两者的区别在于是否获取地址。当搭配 SLAAC 使用时，主机只需要从 DHCPv6 获取 DNS 等信息，因而可以使用无状态 DHCPv6。</p>
<h2 id="dhcpv6-pd-prefix-delegation-前缀委托">DHCPv6 PD (Prefix Delegation, 前缀委托)</h2>
<p>PD 是 <a href="https://datatracker.ietf.org/doc/html/rfc3633">RFC 3633</a> 定义的 DHCPv6 扩展。它用于在网络中分发 IPv6 前缀。</p>
<p>在启用 PD 扩展的情况下，DHCP server 向主机发送一个 IPv6 子网前缀（如 <code>2001:db8::/56</code>）的使用权，并添加路由表以确保将此子网下的地址全部路由到请求前缀的主机。主机可以再对此子网进行划分和分配。</p>
<p>一个典型的 DHCPv6 PD 使用场景是家庭 ISP 网络接入。家庭网关路由器向 ISP DHCP server 请求 IPv6 前缀，然后再通过 SLAAC 在家庭内网中分发此前缀子网中的地址。</p>
<h2 id="总结">总结</h2>
<p>本文简要介绍了 IPv6 地址分配中涉及的一些概念，并阐述了 SLAAC、DHCPv6、DHCPv6 PD 的工作原理。在简化地址管理这一方面，IPv6 可以说做得并不成功，多种标准并存，且存在不同的组合形式，让客户端会有不小的概率无法正确获取 IPv6。</p>
<p>在实际情况中，我们最常预见的 IPv6 分配情况有三种：</p>
<ul>
<li>纯 SLAAC：一般校园网（教育网）属于此类。在实际使用中，笔者发现存在错误配置的内网主机胡乱发送 RA 的情况，导致整个内网所有主机的 IPv6 都错误配置。与此同时，在这种模式下，自行接入的路由器将无法再向下级设备分发 SLAAC GUA，因为 SLAAC 基于的本地链路组播数据包无法被路由器转发（可以通过 IPv6 桥接或者 NAT6 解决，此处不展开说明）。</li>
<li>纯 DHCPv6：一些企业内网会使用此模式，因为 DHCPv6 可以集中管理。这种模式最大的问题是 <a href="https://www.nullzero.co.uk/android-does-not-support-dhcpv6-and-google-wont-fix-that/">Android 不支持 DHCPv6</a>。但在其他操作系统下，此模式运行较为稳定。</li>
<li>SLAAC + DHCPv6 PD：这是家庭 ISP 网络接入最常见的模式，大部分家用路由器都对此做了适配，可以做到开箱即用。</li>
</ul>
<h2 id="参考">参考</h2>
<ul>
<li><a href="https://www.networkacademy.io/ccna/ipv6/stateless-address-autoconfiguration-slaac">IPv6 Stateless Address Auto-configuration (SLAAC)</a></li>
<li><a href="https://datatracker.ietf.org/doc/html/rfc4862">RFC 4862: IPv6 Stateless Address Autoconfiguration</a></li>
<li><a href="https://datatracker.ietf.org/doc/html/rfc8106">RFC 6106: IPv6 Router Advertisement Options for DNS Configuration</a></li>
<li><a href="https://datatracker.ietf.org/doc/html/rfc4941">RFC 4914: Privacy Extensions for Stateless Address Autoconfiguration in IPv6</a></li>
<li><a href="https://datatracker.ietf.org/doc/html/rfc3633">RFC 3633: IPv6 Prefix Options for Dynamic Host Configuration Protocol (DHCP) version 6</a></li>
<li><a href="https://www.nullzero.co.uk/android-does-not-support-dhcpv6-and-google-wont-fix-that/">Android does not support DHCPv6 and Google &lsquo;Won&rsquo;t Fix&rsquo; that</a></li>
<li><a href="https://en.wikipedia.org/wiki/Comparison_of_IPv6_support_in_operating_systems">Comparison of IPv6 support in operating systems</a></li>
</ul>
]]></content:encoded></item><item><title>NFS Performance Tuning</title><link>https://monsoon-cs.moe/zh/2024-02-16-nfs-tuning/</link><pubDate>Fri, 16 Feb 2024 00:00:00 +0000</pubDate><guid>https://monsoon-cs.moe/zh/2024-02-16-nfs-tuning/</guid><description>&lt;h2 id="前言"&gt;前言&lt;/h2&gt;
&lt;p&gt;本文是我在实践中总结出的生产场景下 10 Gbps 网络下的 NFS 性能调优指南，特别是针对&lt;strong&gt;大量小文件&lt;/strong&gt;（Lots of Small Files, LOSF）读写的优化。&lt;/p&gt;
&lt;h2 id="调优"&gt;调优&lt;/h2&gt;
&lt;h3 id="硬件"&gt;硬件&lt;/h3&gt;
&lt;p&gt;网络硬件方面，&lt;strong&gt;带宽&lt;/strong&gt;和&lt;strong&gt;延迟&lt;/strong&gt;两者都很重要。&lt;/p&gt;</description><content:encoded><![CDATA[<h2 id="前言">前言</h2>
<p>本文是我在实践中总结出的生产场景下 10 Gbps 网络下的 NFS 性能调优指南，特别是针对<strong>大量小文件</strong>（Lots of Small Files, LOSF）读写的优化。</p>
<h2 id="调优">调优</h2>
<h3 id="硬件">硬件</h3>
<p>网络硬件方面，<strong>带宽</strong>和<strong>延迟</strong>两者都很重要。</p>
<p>要保证 NFS 的性能，高带宽网络是必要的，10 Gbps 对于生产场景来说是基础要求，更高速的 InfiniBand 或者 RoCE 网络则可按照需求和预算进行选择。</p>
<p>对于<strong>大量小文件</strong>（Lots of Small Files, LOSF）场景来说，<strong>延迟比带宽更重要</strong>。很多性能调优教程都忽略了这一点，只关注了连续读写的性能，即使测试了 4K 随机读写，也使用了<strong>错误的测试方法</strong>（下文给出了正确的测试方法）。</p>
<p>延迟的重要性体现在，如果程序对于小文件的访问是<strong>内秉串行化</strong>的，<strong>延迟会决定串行化 IOPS 的上限</strong>。0.1 ms 的延迟决定了串行化的 IOPS 上限是 10k，而 1 ms 的延迟对应的上限则是 1k。</p>
<p>内秉串行化访问的场景非常多。例如，把家目录放置于 NFS 上，oh-my-zsh 的加载、python 包的加载都是内秉串行化的。1ms 的网络延迟会让这些程序慢到不可接受（例如 <code>import torch</code> 的执行需要 30s 以上）。</p>
<p>使用合格的企业级交换机、恰当配置的网络拓扑，可以尽量降低延迟。同时，光模块、光转电口模块的质量也有可能极大影响延迟（我原来使用的中科光电光转电口模块会引入 0.1ms 的额外延迟，导致 IOPS 下降了 2/3）。</p>
<p>需要注意的是，RDMA 尽管理论上能降低延迟，但实际测试中发现 10 Gbps 以太网和 100 Gbps InfiniBand 的串行化 IOPS 差距并不大，预算有限时只使用以太网也足够。</p>
<p>TODO: 巨型帧</p>
<h3 id="linux-kernel">Linux Kernel</h3>
<p>内核网络参数需要进行调整，以适应高速网络：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="c1"># Ref: https://gist.github.com/mizanRahman/40ba603759bfb5153189ccdc9dbbd1e4</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Disable TCP slow start on idle connections</span>
</span></span><span class="line"><span class="cl"><span class="na">net.ipv4.tcp_slow_start_after_idle</span> <span class="o">=</span> <span class="s">0</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Increase Linux autotuning TCP buffer limits</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Set max to 16MB for 1GE and 32M (33554432) or 54M (56623104) for 10GE</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Don&#39;t set tcp_mem itself! Let the kernel scale it based on RAM.</span>
</span></span><span class="line"><span class="cl"><span class="na">net.core.rmem_max</span> <span class="o">=</span> <span class="s">56623104</span>
</span></span><span class="line"><span class="cl"><span class="na">net.core.wmem_max</span> <span class="o">=</span> <span class="s">56623104</span>
</span></span><span class="line"><span class="cl"><span class="na">net.core.rmem_default</span> <span class="o">=</span> <span class="s">56623104</span>
</span></span><span class="line"><span class="cl"><span class="na">net.core.wmem_default</span> <span class="o">=</span> <span class="s">56623104</span>
</span></span><span class="line"><span class="cl"><span class="na">net.core.optmem_max</span> <span class="o">=</span> <span class="s">40960</span>
</span></span><span class="line"><span class="cl"><span class="na">net.ipv4.tcp_rmem</span> <span class="o">=</span> <span class="s">4096 87380 56623104</span>
</span></span><span class="line"><span class="cl"><span class="na">net.ipv4.tcp_wmem</span> <span class="o">=</span> <span class="s">4096 65536 56623104</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># TCP Congestion Control</span>
</span></span><span class="line"><span class="cl"><span class="na">net.ipv4.tcp_congestion_control</span> <span class="o">=</span> <span class="s">bbr</span>
</span></span><span class="line"><span class="cl"><span class="na">net.core.default_qdisc</span> <span class="o">=</span> <span class="s">cake</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>在服务端和客户端都需要应用这套设置，可以写入 <code>/etc/sysctl.conf</code> 中以持久化。</p>
<h3 id="server-side">Server Side</h3>
<p>NFS server 的线程数可以尽量调大点，服务器负载比较高时可以提升性能，我直接设成了服务器的线程数。修改 <code>/etc/nfs.conf</code>：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[nfsd]</span>
</span></span><span class="line"><span class="cl"><span class="na">threads</span><span class="o">=</span><span class="s">128</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>以下几个 NFS server 参数需要调整：</p>
<ul>
<li><code>async</code>：将同步 IO 操作视为异步。同步读写为主的负载可以大幅提升性能，但服务器崩溃时可能造成数据丢失，对数据完整性有极高要求的情况下不推荐使用；</li>
<li><code>no_subtree_check</code>：对性能没有大影响，但在某些情况下可以提升可靠性（同时有轻微的安全风险）。参见 [1]。</li>
</ul>
<h3 id="client-side">Client Side</h3>
<p>没有特殊的理由时应该默认使用最新的 NFSv4.2，NFSv3 使用 UDP 作为底层传输方式时，在高速网络下会因为 UDP 包序列号问题导致数据损坏，参见 [2]。</p>
<p>以下几个 NFS client 参数需要调整：</p>
<ul>
<li><code>proto=rdma</code>：网络支持 RDMA 时设置；</li>
<li><code>nocto</code>：关闭 close-to-open 缓存一致性语义。NFS 默认行为是关闭文件时会把所有更改写回到服务器。如果对于多客户端之间的文件一致性要求比较高，不推荐使用此选项；</li>
<li><code>ac</code>：启用属性缓存（attribute caching），客户端会缓存文件属性。同样。对于数据一致性要求较高的集群，不推荐使用此选项；</li>
<li><code>fsc</code>：使用 FS-Cache 缓存数据到本地。需要同时<a href="https://github.com/jnsnow/cachefilesd">配置 cachefilesd</a>。奇怪的是我在测试中并没有发现数据被缓存到本地，这可能需要进一步的探究；</li>
<li><code>nconnect=16</code>：设置 NFS client 和 server 间建立 16 条 TCP 连接。NFS client 默认只建立一条 TCP 连接，所有 RPC 复用这条连接。在某些情况下这会限制连续读写的带宽。增大 <code>nconnect</code>（最大值 16）可以解决这个问题。</li>
</ul>
<p>特别的，<code>noatime</code> / <code>relatime</code> 的设置对于 NFS 并无影响 [3]，NFS client 始终会缓存 atime 的更改。</p>
<p>有些教程中会推荐修改 <code>rsize</code> 和 <code>wsize</code>，这两个值在 NFSv4.2 默认协商出的即是最大值 <code>1048576</code>，因而无需手动更改，只需检查一下是否协商正确即可。</p>
<p>根据 [4]，<code>sunrpc.tcp_max_slot_table_entries</code> 可能会影响性能，可以适当调大（默认 <code>2</code>）。在我的测试中，我发现当遇到千万数量级的持续小文件访问负载时，NFS 有时候会卡住。当我把这个参数调大时，此问题得以解决。设置 <code>/etc/modprobe.d/sunrpc.conf</code>：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="na">options sunrpc tcp_slot_table_entries</span><span class="o">=</span><span class="s">16384</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>有时我会遇到 <code>nfsd</code> 占用大量 CPU 且性能急剧下降的问题，同时记录到大量 <code>delegreturn</code> RPC calls。根据 [5]，可以通过禁用 <code>fs.leases-enable</code> 解决，设置 <code>/etc/sysctl.conf</code>：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="na">fs.leases-enable</span> <span class="o">=</span> <span class="s">0</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>当 <code>nfsd</code> 因为种种原因重启后，默认会有 90s 的 grace period 用于锁恢复，这段时间内 <code>nfsd</code> 会拒绝所有 <code>open</code> 请求，在内核日志中显示：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-txt" data-lang="txt"><span class="line"><span class="cl">[1073511.138061] NFSD: starting 90-second grace period (net f0000000)
</span></span></code></pre></td></tr></table>
</div>
</div><p>实践中发现这段时间可以适当调小，以减少 <code>nfsd</code> 重启带来的影响。设置 <code>/etc/default/nfs-kernel-server</code>：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Options for rpc.svcgssd.</span>
</span></span><span class="line"><span class="cl"><span class="nv">RPCSVCGSSDOPTS</span><span class="o">=</span><span class="s2">&#34;--lease-time 10 --grace-time 10&#34;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="测试">测试</h2>
<p>TODO</p>
<h2 id="总结">总结</h2>
<p>TODO</p>
<h2 id="参考">参考</h2>
<p>[1] <a href="https://man.archlinux.org/man/exports.5.en#no_subtree_check">https://man.archlinux.org/man/exports.5.en#no_subtree_check</a></p>
<p>[2] <a href="https://man.archlinux.org/man/nfs.5.en#Using_NFS_over_UDP_on_high-speed_links">https://man.archlinux.org/man/nfs.5.en#Using_NFS_over_UDP_on_high-speed_links</a></p>
<p>[3] <a href="https://man.archlinux.org/man/nfs.5.en#File_timestamp_maintenance">https://man.archlinux.org/man/nfs.5.en#File_timestamp_maintenance</a></p>
<p>[4] <a href="https://learn.microsoft.com/en-us/azure/azure-netapp-files/performance-linux-concurrency-session-slots">https://learn.microsoft.com/en-us/azure/azure-netapp-files/performance-linux-concurrency-session-slots</a></p>
<p>[5] <a href="https://docs.gitlab.com/ee/administration/nfs.html#disable-nfs-server-delegation">https://docs.gitlab.com/ee/administration/nfs.html#disable-nfs-server-delegation</a></p>
]]></content:encoded></item><item><title>Building WireGuard VPN for Machine Learning Server Cluster</title><link>https://monsoon-cs.moe/zh/2024-01-29-wg-for-cluster/</link><pubDate>Mon, 29 Jan 2024 00:00:00 +0000</pubDate><guid>https://monsoon-cs.moe/zh/2024-01-29-wg-for-cluster/</guid><description>&lt;h2 id="motivation"&gt;Motivation&lt;/h2&gt;
&lt;p&gt;机器学习集群需要一个安全的方式向用户暴露服务，以及跨公网服务器互联，为此需要部署 VPN 网络。&lt;/p&gt;
&lt;p&gt;VPN 网络的部署需要考虑如下因素：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;网络拓扑：需要选择合适的拓扑结构以尽可能降低延迟；&lt;/li&gt;
&lt;li&gt;用户管理：可以方便地进行用户的增减和授权；&lt;/li&gt;
&lt;li&gt;使用和维护简单。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="design"&gt;Design&lt;/h2&gt;
&lt;h3 id="网络拓扑"&gt;网络拓扑&lt;/h3&gt;
&lt;p&gt;网络拓扑决定着延迟。&lt;/p&gt;</description><content:encoded><![CDATA[<h2 id="motivation">Motivation</h2>
<p>机器学习集群需要一个安全的方式向用户暴露服务，以及跨公网服务器互联，为此需要部署 VPN 网络。</p>
<p>VPN 网络的部署需要考虑如下因素：</p>
<ol>
<li>网络拓扑：需要选择合适的拓扑结构以尽可能降低延迟；</li>
<li>用户管理：可以方便地进行用户的增减和授权；</li>
<li>使用和维护简单。</li>
</ol>
<h2 id="design">Design</h2>
<h3 id="网络拓扑">网络拓扑</h3>
<p>网络拓扑决定着延迟。</p>
<p>延迟最低的方案显然是 full-mesh，即每一对 peer 之间都有直接的 P2P 连接。但这种拓扑结构的管理复杂度是 $\mathcal{O}(n^2)$ 的，并且每添加一个新的 peer 就需要修改所有其他 peer 的配置文件，还需要解决 NAT 带来的问题，这必须借助一些自动化的软件管理。我尝试了 <a href="https://www.netmaker.io/">Netmaker</a> 和 <a href="https://headscale.net/">Headscale</a>，但它们似乎都无法正确处理学校内的<strong>复杂网络环境</strong>，比如各种企业级路由器使用的 symmetric NAT，<strong>成功建立 P2P 的概率非常之低</strong>。</p>
<p>最终我选择了 <strong>full-mesh 和 hub-and-spoke 相结合的拓扑</strong>。由于服务器数量和 IP 很少变化，手动配置一个服务器间的 full-mesh 网络是可行的。与此同时，提供一个 gateway server 作为用户接入的 hub，用户只需要与 gateway server 建立连接。由于大部分用户其实是在校内使用 VPN 的，因此连接到校内的 gateway server 并转发流量并不会带来太多额外延迟。这种结构可以平衡延迟与管理复杂度，用户的增减和授权也只需要在 gateway server 上操作。</p>
<p><img alt="Network Topology" loading="lazy" src="/2024-01-29-wg-for-cluster/topo.png"></p>
<h3 id="协议选择">协议选择</h3>
<p>流行的 OpenVPN 和 IPSec 都足够优秀，但新兴的 WireGuard 具有无可比拟的配置简单性。对于服务端，WireGuard 可以用几行配置文件定义一个 peer 和路由；对于用户，由于 WireGuard 采用基于密钥对的认证方式，只需要一个配置文件即可接入 VPN 网络，不需要额外的密码记忆和登录操作。</p>
<h3 id="管理方式">管理方式</h3>
<p>出于可预测性和稳定性的考量，我选择了手动配置的方法。服务器间的 full-mesh 网络一次配置后就不需要再频繁更改。而用户管理则通过一个脚本实现，当需要添加一个新用户时，脚本生成密钥对并分配 IP，把公钥和路由信息加入 gateway server 的 peer list 中，然后生成包含私钥和分配的 IP 的配置文件，并发给用户。</p>
<p>Gateway server 上的用户 peer 配置示例：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[Peer]</span>
</span></span><span class="line"><span class="cl"><span class="na">PublicKey</span> <span class="o">=</span> <span class="s">&lt;redacted&gt;</span>
</span></span><span class="line"><span class="cl"><span class="na">AllowedIPs</span> <span class="o">=</span> <span class="s">10.1.x.y/32</span>
</span></span><span class="line"><span class="cl"><span class="na">AllowedIPs</span> <span class="o">=</span> <span class="s">fd01::x:y/128</span>
</span></span><span class="line"><span class="cl"><span class="na">PersistentKeepalive</span> <span class="o">=</span> <span class="s">25</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>用户的接入配置文件示例：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[Interface]</span>
</span></span><span class="line"><span class="cl"><span class="na">PrivateKey</span> <span class="o">=</span> <span class="s">&lt;redacted&gt;</span>
</span></span><span class="line"><span class="cl"><span class="na">Address</span> <span class="o">=</span> <span class="s">10.1.x.y/16</span>
</span></span><span class="line"><span class="cl"><span class="na">Address</span> <span class="o">=</span> <span class="s">fd01::x:y/64</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">[Peer]</span>
</span></span><span class="line"><span class="cl"><span class="na">PublicKey</span> <span class="o">=</span> <span class="s">&lt;redacted&gt;</span>
</span></span><span class="line"><span class="cl"><span class="na">AllowedIPs</span> <span class="o">=</span> <span class="s">10.1.0.0/16  # route all VPN traffic to gateway server</span>
</span></span><span class="line"><span class="cl"><span class="na">AllowedIPs</span> <span class="o">=</span> <span class="s">fd01::/64</span>
</span></span><span class="line"><span class="cl"><span class="na">Endpoint</span> <span class="o">=</span> <span class="s">wg.ustcaigroup.xyz:51820  # gateway server is dual stack</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Endpoint = wg.ustcaigroup.xyz:51820  # IPv4</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Endpoint = wg.ustcaigroup.xyz:51820  # IPv6</span>
</span></span><span class="line"><span class="cl"><span class="na">PersistentKeepalive</span> <span class="o">=</span> <span class="s">25</span>
</span></span></code></pre></td></tr></table>
</div>
</div>]]></content:encoded></item><item><title>Ascend 910B 自定义 PyTorch 算子</title><link>https://monsoon-cs.moe/zh/2023-11-14-ascend-910b-custom-op/</link><pubDate>Tue, 14 Nov 2023 00:00:00 +0000</pubDate><guid>https://monsoon-cs.moe/zh/2023-11-14-ascend-910b-custom-op/</guid><description>&lt;h2 id="环境"&gt;环境&lt;/h2&gt;
&lt;p&gt;本文基于的硬件环境为 Ascend 910B3，基于的软件环境包括 &lt;a href="https://www.hiascend.com/developer/download/community/result"&gt;CANN 7.0-RC1&lt;/a&gt;、&lt;a href="https://repo.huaweicloud.com/kunpeng/archive/Ascend/PyTorch/"&gt;PyTorch 1.11.0&lt;/a&gt;、&lt;a href="https://gitee.com/ascend/pytorch/releases/tag/v5.0.rc3-pytorch1.11.0"&gt;Ascend PyTorch Adapter v5.0.rc3-pytorch1.11.0&lt;/a&gt;。其他 CANN 和 PyTorch 版本上的情况可能略有不同。&lt;/p&gt;</description><content:encoded><![CDATA[<h2 id="环境">环境</h2>
<p>本文基于的硬件环境为 Ascend 910B3，基于的软件环境包括 <a href="https://www.hiascend.com/developer/download/community/result">CANN 7.0-RC1</a>、<a href="https://repo.huaweicloud.com/kunpeng/archive/Ascend/PyTorch/">PyTorch 1.11.0</a>、<a href="https://gitee.com/ascend/pytorch/releases/tag/v5.0.rc3-pytorch1.11.0">Ascend PyTorch Adapter v5.0.rc3-pytorch1.11.0</a>。其他 CANN 和 PyTorch 版本上的情况可能略有不同。</p>
<h2 id="注册过程">注册过程</h2>
<h3 id="ascend-pytorch-adapter-中添加自定义算子">Ascend PyTorch Adapter 中添加自定义算子</h3>
<blockquote>
<p>参考：</p>
<ul>
<li><a href="https://www.hiascend.com/document/detail/zh/canncommercial/70RC1/operatordev/Ascendcopdevg/atlas_ascendc_10_0045.html">https://www.hiascend.com/document/detail/zh/canncommercial/70RC1/operatordev/Ascendcopdevg/atlas_ascendc_10_0045.html</a></li>
<li><a href="https://gitee.com/ascend/samples/tree/master/operator/AddCustomSample/FrameworkLaunch/PytorchInvocation">https://gitee.com/ascend/samples/tree/master/operator/AddCustomSample/FrameworkLaunch/PytorchInvocation</a></li>
</ul>
</blockquote>
<p>在 <code>torch_npu/csrc/aten/npu_native_functions.yaml</code> 中添加 <code>npu_add_custom</code> 函数：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">custom</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">func</span><span class="p">:</span><span class="w"> </span><span class="l">npu_add_custom(Tensor x, Tensor y) -&gt; Tensor </span><span class="w"> </span><span class="c"># 添加的函数</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>在 <code>torch_npu/csrc/aten/ops/op_api</code> 中添加 <code>AddCustomKernelNpu.cpp</code> 文件：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;torch/csrc/autograd/custom_function.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&#34;torch_npu/csrc/framework/utils/OpAdapter.h&#34;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&#34;torch_npu/csrc/aten/NPUNativeFunctions.h&#34;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&#34;torch_npu/csrc/aten/ops/op_api/op_api_common.h&#34;</span><span class="cp">
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">namespace</span> <span class="n">at_npu</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">namespace</span> <span class="n">native</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">using</span> <span class="n">torch</span><span class="o">::</span><span class="n">autograd</span><span class="o">::</span><span class="n">Function</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">using</span> <span class="n">torch</span><span class="o">::</span><span class="n">autograd</span><span class="o">::</span><span class="n">AutogradContext</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">at</span><span class="o">::</span><span class="n">Tensor</span> <span class="n">NPUNativeFunctions</span><span class="o">::</span><span class="n">npu_add_custom</span><span class="p">(</span><span class="k">const</span> <span class="n">at</span><span class="o">::</span><span class="n">Tensor</span><span class="o">&amp;</span> <span class="n">x</span><span class="p">,</span> <span class="k">const</span> <span class="n">at</span><span class="o">::</span><span class="n">Tensor</span><span class="o">&amp;</span> <span class="n">y</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">at</span><span class="o">::</span><span class="n">Tensor</span> <span class="n">result</span> <span class="o">=</span> <span class="n">OpPreparation</span><span class="o">::</span><span class="n">ApplyTensor</span><span class="p">(</span><span class="n">x</span><span class="p">);</span> <span class="c1">// 创建输出内存
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// calculate the output result of the NPU
</span></span></span><span class="line"><span class="cl">        <span class="n">EXEC_NPU_CMD</span><span class="p">(</span><span class="n">aclnnAddCustom</span><span class="p">,</span> <span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">result</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">result</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span> <span class="c1">// namespace native
</span></span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="c1">// namespace at_npu
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>之后重新编译安装 <code>torch_npu</code>。</p>
<h3 id="cann-中添加自定义算子的实现">CANN 中添加自定义算子的实现</h3>
<blockquote>
<p>参考：</p>
<ul>
<li><a href="https://www.hiascend.com/document/detail/zh/canncommercial/70RC1/operatordev/Ascendcopdevg/atlas_ascendc_10_0023.html">https://www.hiascend.com/document/detail/zh/canncommercial/70RC1/operatordev/Ascendcopdevg/atlas_ascendc_10_0023.html</a></li>
</ul>
</blockquote>
<p>首先定义算子描述文件 <code>add_custom.json</code>：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;op&#34;</span><span class="p">:</span> <span class="s2">&#34;AddCustom&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;language&#34;</span><span class="p">:</span> <span class="s2">&#34;cpp&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;input_desc&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">            <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;x&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="nt">&#34;param_type&#34;</span><span class="p">:</span> <span class="s2">&#34;required&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="nt">&#34;format&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">                    <span class="s2">&#34;ND&#34;</span>
</span></span><span class="line"><span class="cl">                <span class="p">],</span>
</span></span><span class="line"><span class="cl">                <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">                    <span class="s2">&#34;fp16&#34;</span>
</span></span><span class="line"><span class="cl">                <span class="p">]</span>
</span></span><span class="line"><span class="cl">            <span class="p">},</span>
</span></span><span class="line"><span class="cl">            <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;y&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="nt">&#34;param_type&#34;</span><span class="p">:</span> <span class="s2">&#34;required&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="nt">&#34;format&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">                    <span class="s2">&#34;ND&#34;</span>
</span></span><span class="line"><span class="cl">                <span class="p">],</span>
</span></span><span class="line"><span class="cl">                <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">                    <span class="s2">&#34;fp16&#34;</span>
</span></span><span class="line"><span class="cl">                <span class="p">]</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">],</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;output_desc&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">            <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;z&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="nt">&#34;param_type&#34;</span><span class="p">:</span> <span class="s2">&#34;required&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="nt">&#34;format&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">                    <span class="s2">&#34;ND&#34;</span>
</span></span><span class="line"><span class="cl">                <span class="p">],</span>
</span></span><span class="line"><span class="cl">                <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">                    <span class="s2">&#34;fp16&#34;</span>
</span></span><span class="line"><span class="cl">                <span class="p">]</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">]</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>执行</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">msopgen gen -i add_custom.json -c ai_core-Ascend910B3 -f pytorch -out . -lan cpp
</span></span></code></pre></td></tr></table>
</div>
</div><p>生成算子工程：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-txt" data-lang="txt"><span class="line"><span class="cl">AddCustom
</span></span><span class="line"><span class="cl">├── build.sh
</span></span><span class="line"><span class="cl">├── cmake 
</span></span><span class="line"><span class="cl">│   ├── config.cmake
</span></span><span class="line"><span class="cl">│   ├── func.cmake
</span></span><span class="line"><span class="cl">│   ├── intf.cmake
</span></span><span class="line"><span class="cl">│   ├── makeself.cmake
</span></span><span class="line"><span class="cl">│   └── util
</span></span><span class="line"><span class="cl">├── CMakeLists.txt
</span></span><span class="line"><span class="cl">├── CMakePresets.json          // 修改 ASCEND_CANN_PACKAGE_PATH
</span></span><span class="line"><span class="cl">├── framework
</span></span><span class="line"><span class="cl">├── op_host
</span></span><span class="line"><span class="cl">│   ├── add_custom_tiling.h    // 定义 length 和 tiling 相关信息
</span></span><span class="line"><span class="cl">│   ├── add_custom.cpp         // 算子 host 侧实现
</span></span><span class="line"><span class="cl">│   ├── CMakeLists.txt
</span></span><span class="line"><span class="cl">├── op_kernel
</span></span><span class="line"><span class="cl">│   ├── CMakeLists.txt
</span></span><span class="line"><span class="cl">│   ├── add_custom.cpp         // 算子 kernel 侧实现
</span></span><span class="line"><span class="cl">└── scripts
</span></span></code></pre></td></tr></table>
</div>
</div><p><code>CMakePresets.json</code> 中修改 <code>ASCEND_CANN_PACKAGE_PATH</code> 为 CANN 安装路径。</p>
<p><code>op_host/add_custom_tiling.h</code> 的内容如下（简单实现）：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&#34;register/tilingdata_base.h&#34;</span><span class="cp">
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">namespace</span> <span class="n">optiling</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="n">BEGIN_TILING_DATA_DEF</span><span class="p">(</span><span class="n">AddCustomTilingData</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">TILING_DATA_FIELD_DEF</span><span class="p">(</span><span class="kt">uint32_t</span><span class="p">,</span> <span class="n">size</span><span class="p">);</span>  <span class="c1">// 定义 tensor size
</span></span></span><span class="line"><span class="cl"><span class="n">END_TILING_DATA_DEF</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">REGISTER_TILING_DATA_CLASS</span><span class="p">(</span><span class="n">AddCustom</span><span class="p">,</span> <span class="n">AddCustomTilingData</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p><code>op_host/add_custom.cpp</code> 中修改算子调用时的 <code>block_dim</code>：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="n">context</span><span class="o">-&gt;</span><span class="n">SetBlockDim</span><span class="p">(</span><span class="mi">20</span><span class="p">);</span> <span class="c1">// 910B3 的 block_dim
</span></span></span></code></pre></td></tr></table>
</div>
</div><p><code>op_kernel/add_custom.cpp</code> 是算子的具体实现：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&#34;kernel_operator.h&#34;</span><span class="cp">
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cp">#ifdef __DAV_C220_VEC__
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">extern</span> <span class="s">&#34;C&#34;</span> <span class="n">__global__</span> <span class="n">__aicore__</span> <span class="kt">void</span> <span class="n">add_custom</span><span class="p">(</span><span class="n">GM_ADDR</span> <span class="n">x</span><span class="p">,</span> <span class="n">GM_ADDR</span> <span class="n">y</span><span class="p">,</span> <span class="n">GM_ADDR</span> <span class="n">z</span><span class="p">,</span> <span class="n">GM_ADDR</span> <span class="n">workspace</span><span class="p">,</span> <span class="n">GM_ADDR</span> <span class="n">tiling</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">GET_TILING_DATA</span><span class="p">(</span><span class="n">tiling_data</span><span class="p">,</span> <span class="n">tiling</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint32_t</span> <span class="n">M</span> <span class="o">=</span> <span class="n">tiling_data</span><span class="p">.</span><span class="n">size</span><span class="p">;</span>  <span class="c1">// 从 tiling_data 中获取 tensor size
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cp">#else
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// 重要：CANN 会尝试不同的 ccec 编译参数以推断算子的类型（VEC、CUBE、MIXED），如果不创建一个 stub 函数将会编译失败
</span></span></span><span class="line"><span class="cl"><span class="k">extern</span> <span class="s">&#34;C&#34;</span> <span class="n">__global__</span> <span class="n">__aicore__</span> <span class="kt">void</span> <span class="n">add_custom</span><span class="p">(</span><span class="n">GM_ADDR</span> <span class="n">x</span><span class="p">,</span> <span class="n">GM_ADDR</span> <span class="n">y</span><span class="p">,</span> <span class="n">GM_ADDR</span> <span class="n">z</span><span class="p">,</span> <span class="n">GM_ADDR</span> <span class="n">workspace</span><span class="p">,</span> <span class="n">GM_ADDR</span> <span class="n">tiling</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">pip_barrier</span><span class="p">(</span><span class="n">PIPE_ALL</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cp">#endif
</span></span></span></code></pre></td></tr></table>
</div>
</div><h3 id="编译部署">编译部署</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ bash build.sh
</span></span><span class="line"><span class="cl">$ ./custom_opp_euleros_aarch64.run
</span></span></code></pre></td></tr></table>
</div>
</div><p>PyTorch 中调用：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">torch</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">torch_npu</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ...</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">z</span> <span class="o">=</span> <span class="n">torch</span><span class="o">.</span><span class="n">npu_add_custom</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">)</span>  <span class="c1"># 由于是运行时编译，第一次运行时需要等待编译</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="注册原理">注册原理</h2>
<p>TODO</p>
<h2 id="参考">参考</h2>
<p>TODO</p>
]]></content:encoded></item><item><title>我的 TOEFL 经验</title><link>https://monsoon-cs.moe/zh/2023-11-05-toefl-exp/</link><pubDate>Sun, 05 Nov 2023 00:00:00 +0000</pubDate><guid>https://monsoon-cs.moe/zh/2023-11-05-toefl-exp/</guid><description>&lt;h2 id="前言"&gt;前言&lt;/h2&gt;
&lt;p&gt;作为高考以来带给我最大焦虑感的考试，TOEFL 让我 2023 年大部分时间在黑暗中度过，我对其的时间、金钱投入也是最大的。&lt;/p&gt;
&lt;p&gt;一开始定下总分 100、口语 20 的目标，中间经历了无数天自信心丧失、被焦虑情绪淹没、口语练到舌头打结，最终在 2023 年 11 月 3 日查询到了满意的成绩。&lt;/p&gt;</description><content:encoded><![CDATA[<h2 id="前言">前言</h2>
<p>作为高考以来带给我最大焦虑感的考试，TOEFL 让我 2023 年大部分时间在黑暗中度过，我对其的时间、金钱投入也是最大的。</p>
<p>一开始定下总分 100、口语 20 的目标，中间经历了无数天自信心丧失、被焦虑情绪淹没、口语练到舌头打结，最终在 2023 年 11 月 3 日查询到了满意的成绩。</p>
<p>我写下此文既作为自己过去的总结，也希望能帮助到可能看到这篇文章的人。</p>
<p>我参加的场次和得分情况：</p>
<table>
	<thead>
			<tr>
					<th style="text-align: center">考试时间</th>
					<th style="text-align: center">总分</th>
					<th style="text-align: center">阅读</th>
					<th style="text-align: center">听力</th>
					<th style="text-align: center">口语</th>
					<th style="text-align: center">写作</th>
					<th style="text-align: center">备注</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td style="text-align: center">2023.7.22</td>
					<td style="text-align: center">89</td>
					<td style="text-align: center">27</td>
					<td style="text-align: center">24</td>
					<td style="text-align: center">16</td>
					<td style="text-align: center">22</td>
					<td style="text-align: center">改革前</td>
			</tr>
			<tr>
					<td style="text-align: center">2023.8.15</td>
					<td style="text-align: center">89</td>
					<td style="text-align: center">28</td>
					<td style="text-align: center">25</td>
					<td style="text-align: center">17</td>
					<td style="text-align: center">19</td>
					<td style="text-align: center">这场起为改革后</td>
			</tr>
			<tr>
					<td style="text-align: center">2023.9.16</td>
					<td style="text-align: center">96</td>
					<td style="text-align: center">29</td>
					<td style="text-align: center">27</td>
					<td style="text-align: center">19</td>
					<td style="text-align: center">21</td>
					<td style="text-align: center"></td>
			</tr>
			<tr>
					<td style="text-align: center">2023.10.14</td>
					<td style="text-align: center">96</td>
					<td style="text-align: center">30</td>
					<td style="text-align: center">24</td>
					<td style="text-align: center">19</td>
					<td style="text-align: center">23</td>
					<td style="text-align: center"></td>
			</tr>
			<tr>
					<td style="text-align: center">2023.10.28</td>
					<td style="text-align: center">101</td>
					<td style="text-align: center">28</td>
					<td style="text-align: center">27</td>
					<td style="text-align: center">22</td>
					<td style="text-align: center">24</td>
					<td style="text-align: center"></td>
			</tr>
			<tr>
					<td style="text-align: center">MyBest</td>
					<td style="text-align: center">103</td>
					<td style="text-align: center">30</td>
					<td style="text-align: center">27</td>
					<td style="text-align: center">22</td>
					<td style="text-align: center">24</td>
					<td style="text-align: center"></td>
			</tr>
	</tbody>
</table>
<p>用到的学习材料：</p>
<ul>
<li>背单词：<a href="https://www.maimemo.com/">墨墨背单词</a></li>
<li>听力口语训练：<a href="https://toefl.kmf.com/">学而思考满分</a>、<a href="https://tpo.xdf.cn/">新东方 TOEFL</a>、某宝买的 TPO 1~74 所有口语题目</li>
<li>口语参考：<a href="https://book.douban.com/subject/30300871/">新东方《托福口语白皮书》</a></li>
<li>写作参考：<a href="https://book.douban.com/subject/26338897/">新东方《托福写作白皮书》</a>，改革后<a href="https://zhuanlan.zhihu.com/p/648415673">所有学术交流写作真题及范文</a></li>
</ul>
<h2 id="阅读">阅读</h2>
<p>对于大部分中国学生而言这是最简单的部分，一个合格的 211 以上的学生必定能轻松应对。</p>
<p>我在考前只做了两篇适应一下做题节奏，第一次考就取得了 27 分，之后一直稳定，并在第四次考到了满分。个人感觉 TOEFL 阅读难度甚至低于江苏高考和六级阅读。虽然我第一次考试前背了许多单词，但那更多是为了 GRE 准备的，TOEFL 阅读本身基本无词汇方面的挑战。</p>
<p>虽然高分并不难，但满分还需要一点运气。我考满分的那次，两篇阅读的话题分别是「地球早期的海洋与大气」、「农业革命与灌溉」，都是我非常熟悉的话题。这种情况下的阅读就是简单模式。</p>
<h2 id="听力">听力</h2>
<p>TOEFL 奇葩的考试模式，让听力、口语、写作都考察你的听力能力。但<strong>这三部分听力其实是完全不一样的</strong>：</p>
<ul>
<li>听力部分本身：
<ul>
<li>Conversation：难度较高，日常对话一直是我薄弱的地方，连读、吞音等现象最多，语速也较快；</li>
<li>Lecture：难度一般，虽然看似很长，但其实语速较慢，容错也高，一句话没听清完全可以根据上下文 infer；</li>
</ul>
</li>
<li>综合口语：这部分的听力其实难度最高，需要尽量多捕获细节并记下充分的笔记，我口语基础本身很差，难上加难；</li>
<li>综合写作：难度最低，一开始会让你读完一篇阅读材料熟悉话题，并且听力结构死板，逻辑清晰，语速较慢。</li>
</ul>
<p>但不得不说，**经过恰当的训练，听力部分也是很容易提分、考到高分的。**我集中大量训练了 20 天左右，另外还有大概 30 天零零碎碎的训练（和别的事情混在一起）。</p>
<p>关于听力，最重要的一点是，**必须要摸索出适合你自己的做题方式。**很多学习资料会强调听力时如何正确记笔记，我一开始也是那样训练的，但考完第一次后我发现这种方法并不适合我，记笔记会分散你的注意力，听力内容跟丢（不再能把握上下文的逻辑关系）的概率会极大增加。</p>
<p>我的总结是，<strong>笔记适合记录细节，人脑适合记住逻辑。</strong></p>
<p>**TOEFL 纯听力部分其实并不注重细节，反而更考察你对听力材料的整体把握。**后来我 20 天的专门训练中，我就彻底抛弃了笔记，效果很好。需要说明的是，后来我发现遇到细节密度比较高的时候，偶尔记笔记还是有用的，能帮助你避免走神，记下的内容其实没用，我考场上从没看过。在这里，记笔记其实只是为了强化人脑记忆，并不是一种外部信息存储方式。</p>
<p>我自己使用的听力训练法：第一遍做题，第二遍重听，第三遍看着文字内容听，之后再听若干次，直到你能听清每个细节为止。专门训练时每篇听力我大概要花 20~40 分钟不等，每天练至少 6 篇。</p>
<p>同样，话题熟悉度会很大程度上影响发挥。我第一次得 27 分的那场，有篇 lecture 是讲的经典故事「胶带手撕石墨烯得诺贝尔奖」，虽然我很熟悉并且做得顺风顺水，但内容确实有点偏专业，有许多物理学专业词汇，涉及石墨烯的分层结构、各向异性的导电性的原理。由于 TOEFL 听力 lecture 还是以理工科为主，摸鱼时从知乎 B 站上学到的没用知识，甚至中学时代看过的一些科普读物，都可能以一种意想不到的方式帮助你，广博的知识面会让你事半功倍。但与此同时，遇到不熟悉的话题就很麻烦，我第四场考试听力只得 24 分，原因就是遇到了一个 literature 话题，大部分内容没听明白。</p>
<p><strong>2023 年 7 月改版后</strong>听力有个坑点：由于取消了中场休息，有些人做得快，会在你听听力时就开始讲口语，产生严重干扰。第二场考试前虽然我专项训练了，但听力仍然只有 25 分，就是踩了这个坑。</p>
<p>避免此坑的方法是，所有的 direction 部分全部快速跳过，阅读部分可以剩两分钟提前结束，这样你可以成为全场第一个讲口语的人，<del>让别人被你干扰</del>。</p>
<blockquote>
<p><del>宁叫我负天下人，不叫天下人负我。</del></p>
</blockquote>
<h2 id="口语">口语</h2>
<p>看分数就知道这是最折磨我的一部分，甚至后两场就只是为口语考的（口语没到 20 申请时非常危险）。</p>
<p>口语专项我高强度训练大概 30 天，非专项训练的天数加起来数不清了。</p>
<p>对于像我这种口语基础很差的人来说，大量的训练可以保证你的分数能在 20 左右，之后还是要看运气和临场发挥。</p>
<p><strong>TOEFL 口语与其说是口语考试，不如说是大综合</strong>。对我个人而言，口语部分的阅读和听力要求甚至高于阅读和听力部分本身：</p>
<ul>
<li>task2、task3 的阅读部分要求<strong>速读能力</strong>，个人感觉没有 4 words/s 是搞不定的，而且你<strong>不会有没读通后回滚的机会</strong>。而阅读部分其实完全可以照我平时看论文的速度去看，一句话没看明白也能多看几遍。</li>
<li>综合口语的听力要求你记下细节，相比之下听力部分很多时候只要记下逻辑就行。记细节就必须依赖笔记，平衡好笔记、接收信息和把握整体逻辑，是最为困难的。</li>
</ul>
<h3 id="独立口语">独立口语</h3>
<p>素材积累是有必要的，但数量不在多，我只准备了 10 个常用的，重要的是一定要熟练运用，看到题目需要快速反应出来可以套什么素材。这可以去专门练习学而思考满分上的<a href="https://toefl.kmf.com/speak/gold/1">口语黄金 80 题</a>。</p>
<p>同时素材也不是万能的，独立口语不可避免地带有许多随机因素，经常需要临场发挥编故事，这时用中文快速想好后翻译成英文（写下几个关键词，说的时候连词成句）会比较快。</p>
<h3 id="综合口语">综合口语</h3>
<p>对我来说整场考试难度最高部分，每次考到这基本就肾上腺素爆发。</p>
<p>**如何应对综合口语是我花最多时间训练的部分，没有什么捷径，必须要自己找感觉、找经验。**我在这里说一下我总结出的适合我的经验：</p>
<ul>
<li>阅读时：task2、task3 虽然给了你 45s 阅读，<strong>但最好只用 15s 就扫完，并找出关键句（非关键句直接不看），之后把关键句抄下来</strong>（不必一字不差，但尽量完整，可以直接读，不必组织语言的那种）。这样做的好处是，我在准备时间可以直接快速读一遍，正式说的时候一开始不仅流畅而且节省时间；</li>
<li>听力时：尽可能记下细节，但必须要同时排除非重点，重点部分则同样记下关键词/句。与此同时，记笔记绝对不能影响到接收信息本身；</li>
<li>准备时：一边把要说的内容读出来（不要默念，默念会让你产生你已经说流畅了的错觉），一边圈出有用的信息（或者划掉无用信息），用箭头整理出一条说的线，必要时在一些关键词间写下填充内容，降低临时组织语言的负担；</li>
<li>正式说：以保证流畅度为优先目标，时间不够了、卡住了时可以丢弃部分细节。结结巴巴、重复一句话不仅会降低分数，还会浪费时间。</li>
</ul>
<p>**无论什么情况下，千万不能过度紧张。**过度紧张会让你思考变慢，也会极大增加说的时候的卡顿。我得 22 分的那场，考口语时就处于比较放松的状态。</p>
<p>综合口语我个人的训练方法：第一遍正常做，然后紧接着重说一遍，之后看解答，然后不停重复说直到能非常流畅。这种训练方式下一篇大概需要 15~30 分钟，我一天练 10 篇。</p>
<h2 id="写作">写作</h2>
<p>**没有感情，全是套路。**实际上我根本没有在写作训练上投入多少时间，一般的英语基础加上适当的技巧就能拿到至少 22。</p>
<p>需要注意的是，<strong>不要让打字速度拖你后腿</strong>。我是打字速度比较慢、并且 typo 很多的人，前两场次这确实影响到了我，不过后来熟练了也没问题了。</p>
<h3 id="综合写作">综合写作</h3>
<p>综合写作的阅读可以定定心心读，给的时间甚至够你看两遍，也不用记笔记。听力部分也很简单，有阅读做铺垫让你熟悉话题，同时结构死板，逻辑清晰，语速较慢，记下重要细节并不困难。</p>
<p>要注意的是<strong>不要死背模版</strong>，把考试时间浪费在打模版上得不偿失，保证逻辑清晰结构工整即可。时间应该用在尽量多还原细节上，language use 用高考级别的词汇就行了，足够拿到 24 分。</p>
<h3 id="学术交流写作">学术交流写作</h3>
<p>2023 年 7 月改革后去掉了独立写作，换成了学术交流写作，时间缩短到 10 分钟。第二次考试写作只有 19，是因为我心大完全没练新题型就上了，结果就是完全没按照要求答。</p>
<p>后来我花了半天专门训练了学术交流写作，基本上道。考试时其实只要看 professor 的提问，一堆废话不用看，之后扫一眼两个 student sample answers，找出核心观点，这是为了避免观点撞车，具体内容也不用看完，之后就可以开始写了。</p>
<p>我个人的模版如下：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-txt" data-lang="txt"><span class="line"><span class="cl">From my perspective, &lt;我的观点&gt;.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Although &lt;找一个你反对的 sample answer 抄上他的观点&gt;，&lt;简单说一下我的观点的 advantage&gt;.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">&lt;详细展开，可以用些例子，也可以指出你反对的观点的不足，60～70 words 足够&gt;.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">&lt;（可选，我个人喜欢的表达方式）有时候可以说一下我的 method 其实可以更好地达到我反对的 method 的目标&gt;.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">So, &lt;总结观点&gt;.
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="总结">总结</h2>
<p><strong>不积跬步，无以至千里。</strong></p>
<p>对我个人而言，TOEFL 让我反思了大学以来的学习模式。本科时的课程要么是我已经熟悉的或者有基础的，要么是考前突击的。TOEFL 这种语言考试没有捷径（除非你是语言天才），必须从 Day 1 开始一点点训练，一点点找感觉、找经验。在这个过程种除了题目的障碍，更多还有负面情绪的障碍，找一些你信任的、同时愿意倾听你的人分享情绪非常有帮助。</p>
]]></content:encoded></item><item><title>利用 SSH 反向隧道登录 BitaHub 中的容器并长期占用 GPU</title><link>https://monsoon-cs.moe/zh/2023-10-20-bitahub/</link><pubDate>Fri, 20 Oct 2023 00:00:00 +0000</pubDate><guid>https://monsoon-cs.moe/zh/2023-10-20-bitahub/</guid><description>&lt;h2 id="问题"&gt;问题&lt;/h2&gt;
&lt;p&gt;每年的 CVPR 前 GPU 总是供不应求，需要从其他地方借卡。USTC 有一个供校内用户使用的 &lt;a href="https://bitahub.ustc.edu.cn/"&gt;BitaHub&lt;/a&gt;，但它同样有 CVPR 前一卡难求的问题，同时基于任务提交的使用模式也非常不方便，提交占用多卡的任务经常需要漫长的排队，数据管理方式更是反人类。&lt;/p&gt;</description><content:encoded><![CDATA[<h2 id="问题">问题</h2>
<p>每年的 CVPR 前 GPU 总是供不应求，需要从其他地方借卡。USTC 有一个供校内用户使用的 <a href="https://bitahub.ustc.edu.cn/">BitaHub</a>，但它同样有 CVPR 前一卡难求的问题，同时基于任务提交的使用模式也非常不方便，提交占用多卡的任务经常需要漫长的排队，数据管理方式更是反人类。</p>
<p>作为组里的服务器管理员，为了让自己在 CVPR 前活得轻松点，避免重蹈 2021 年 CVPR 前疲于应对资源调配的覆辙，有必要改善 BitaHub 的使用体验：</p>
<ol>
<li>如何长期占用显卡避免重复排队（虽然略不道德，但实属无奈之举）；</li>
<li>如何方便地从我们的服务器读取数据，而不是被迫使用 BitaHub 反人类的数据管理模式；</li>
<li>如何尽量使 BitaHub 的 GPU 使用体验接近组里的服务器，降低迁移成本，提高资源调度的灵活性。</li>
</ol>
<h2 id="思路">思路</h2>
<p>BitaHub 中的任务是以 docker 容器的方式运行的，因而给了我们在容器里配置我们想要的环境的可能，只需要通过某种方式登录 ssh 到容器中。</p>
<p>经过研究发现，只要启动命令不停止运行，BitaHub 中的容器就会长期运行，不释放 GPU 资源。<strong>同时 BitaHub 中的容器是可以联网的</strong>，而且 BitaHub 的网页上还贴心地给出了每个任务容器中 root 用户的 ssh 私钥。</p>
<p>这些给了我们利用的机会，只需要在容器内运行一个 tunnel 程序以让外部得以访问容器中的 22 端口，就能登录并长期占用资源。同时由于容器联网，也可以直接挂载校内其他服务器的文件系统。</p>
<h2 id="解决方案">解决方案</h2>
<p>最终选择的 tunnel 程序是 <code>ssh</code>，它可以创建反向隧道：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">ssh -i &lt;key_file&gt; -F none -o <span class="s2">&#34;StrictHostKeyChecking no&#34;</span> -o <span class="s2">&#34;ServerAliveInterval 15&#34;</span> -v -N -R &lt;port&gt;:localhost:22 jump@&lt;jumpserver&gt;
</span></span></code></pre></td></tr></table>
</div>
</div><p>在 <code>jumpserver</code> 中配置用户 <code>jump</code> 并允许特定私钥登录，然后用某种方式把私钥传递进容器（可以直接打包进镜像，但我选择了更方便的方式——创建一个 BitaHub 数据集存放，每个任务添加这个数据集即可）。</p>
<p>容器的启动命令就是上述命令（考虑到网络波动，可以套一层 <code>while true</code> 循环或者用 <code>autossh</code> 自动重连），启动后就在 <code>&lt;jumpserver&gt;</code> 上的 <code>&lt;port&gt;</code> 端口创建了一个反向隧道，<code>&lt;port&gt;</code> 被映射到了容器内的 <code>22</code> 端口。</p>
<p>可以在 <code>&lt;jumpserver&gt;</code> 的 <code>sshd_config</code> 中配置 <code>GatewayPorts yes</code>，这样反向隧道就会监听 <code>0.0.0.0</code> 而不是 <code>127.0.0.1</code>。不这样做的话我就要在 <code>&lt;jumpserver&gt;</code> 上给每个人创建用户，或者每个端口用 <code>iptables</code> 转发，但这太过繁琐。绑定 <code>0.0.0.0</code> 则可以直接从现有的 VPN 网络中访问。</p>
<p>挂载文件系统的方式有很多选择，考虑到安全性和便捷性，我选择了 SSHFS。NFS 直接暴露于公网过于危险，而 NFS 用户验证的配置又过于繁琐。同时 BitaHub 运行容器的内核既没加载 <code>wireguard</code> kmod 也没映射 <code>/dev/net/tun</code>，因此无法利用 VPN 保护数据安全。SSHFS 可以直接复用现存的用户认证方式，而 SSH 流量本身也更容易被潜在的机房防火墙放过。</p>
<p>使用如下命令挂载 SSHFS：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">sshfs -o reconnect,ServerAliveInterval<span class="o">=</span>15,ServerAliveCountMax<span class="o">=</span>30,ssh_command<span class="o">=</span><span class="s1">&#39;ssh -p &lt;dataserver_port&gt; -i &lt;key_file&gt;&#39;</span> &lt;user&gt;@&lt;dataserver&gt;:/path /path
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="后记">后记</h2>
<p>TODO</p>
]]></content:encoded></item><item><title>Nginx 启用 QUIC 并和 SNI 分流共存</title><link>https://monsoon-cs.moe/zh/2023-09-26-nginx-quic-with-ssl-preread/</link><pubDate>Tue, 26 Sep 2023 00:00:00 +0000</pubDate><guid>https://monsoon-cs.moe/zh/2023-09-26-nginx-quic-with-ssl-preread/</guid><description>&lt;h2 id="问题"&gt;问题&lt;/h2&gt;
&lt;p&gt;Nginx 自从 1.25.0 版本以来对 QUIC 的支持&lt;a href="https://nginx.org/en/docs/quic.html"&gt;已被合并入 mainline&lt;/a&gt;，对于想体验的用户而言可以直接使用官方发布的 &lt;code&gt;nginx&lt;/code&gt; docker 镜像，非常方便。&lt;/p&gt;
&lt;p&gt;但是我的服务器上的 nginx 使用了 &lt;a href="https://nginx.org/en/docs/stream/ngx_stream_ssl_preread_module.html"&gt;SNI 分流&lt;/a&gt;，源于 &lt;a href="https://github.com/ihciah/shadow-tls"&gt;Shadow TLS&lt;/a&gt; 和 &lt;a href="https://github.com/XTLS/REALITY"&gt;Xray Reality&lt;/a&gt; 等新一代基于 TLS 的代理协议的需求。这些代理协议并不能由 nginx 代为处理 TLS 层（和之前可以使用 gPRC/WebSocket 等作为数据传输方式的协议不同），但为了实现最好的伪装效果，使用 &lt;code&gt;443/tcp&lt;/code&gt; 端口是有必要的（伪装的白名单目标网站一般情况下也只会在 &lt;code&gt;443/tcp&lt;/code&gt; 端口开放 HTTPS 服务）。因此 &lt;code&gt;443/tcp&lt;/code&gt; 端口的复用是必要的。&lt;/p&gt;</description><content:encoded><![CDATA[<h2 id="问题">问题</h2>
<p>Nginx 自从 1.25.0 版本以来对 QUIC 的支持<a href="https://nginx.org/en/docs/quic.html">已被合并入 mainline</a>，对于想体验的用户而言可以直接使用官方发布的 <code>nginx</code> docker 镜像，非常方便。</p>
<p>但是我的服务器上的 nginx 使用了 <a href="https://nginx.org/en/docs/stream/ngx_stream_ssl_preread_module.html">SNI 分流</a>，源于 <a href="https://github.com/ihciah/shadow-tls">Shadow TLS</a> 和 <a href="https://github.com/XTLS/REALITY">Xray Reality</a> 等新一代基于 TLS 的代理协议的需求。这些代理协议并不能由 nginx 代为处理 TLS 层（和之前可以使用 gPRC/WebSocket 等作为数据传输方式的协议不同），但为了实现最好的伪装效果，使用 <code>443/tcp</code> 端口是有必要的（伪装的白名单目标网站一般情况下也只会在 <code>443/tcp</code> 端口开放 HTTPS 服务）。因此 <code>443/tcp</code> 端口的复用是必要的。</p>
<p>如果要让 SNI 分流和 QUIC 共存，在原来的 SNI 分流配置上只需要给每个 server 加上 <code>listen 443 quic</code> 即可。示例配置如下。</p>
<h2 id="配置">配置</h2>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span><span class="lnt">47
</span><span class="lnt">48
</span><span class="lnt">49
</span><span class="lnt">50
</span><span class="lnt">51
</span><span class="lnt">52
</span><span class="lnt">53
</span><span class="lnt">54
</span><span class="lnt">55
</span><span class="lnt">56
</span><span class="lnt">57
</span><span class="lnt">58
</span><span class="lnt">59
</span><span class="lnt">60
</span><span class="lnt">61
</span><span class="lnt">62
</span><span class="lnt">63
</span><span class="lnt">64
</span><span class="lnt">65
</span><span class="lnt">66
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-nginx" data-lang="nginx"><span class="line"><span class="cl"><span class="k">http</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="c1"># ...
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kn">server</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kn">server_name</span> <span class="s">example.com</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1"># 443/tcp 已经被 nginx stream 占用，不能再次监听
</span></span></span><span class="line"><span class="cl">        <span class="c1"># listen 443 ssl http2 reuseport so_keepalive=on;
</span></span></span><span class="line"><span class="cl">        <span class="c1"># listen [::]:443 ssl http2 reuseport so_keepalive=on;
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1"># 监听 443/udp 端口并启用 QUIC
</span></span></span><span class="line"><span class="cl">        <span class="c1"># ref: https://nginx.org/en/docs/http/ngx_http_v3_module.html
</span></span></span><span class="line"><span class="cl">        <span class="kn">listen</span> <span class="mi">443</span> <span class="s">quic</span> <span class="s">reuseport</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="kn">listen</span> <span class="s">[::]:443</span> <span class="s">quic</span> <span class="s">reuseport</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1"># 监听 unix domain socket 以接受 stream 传送过来的连接，也可以使用本地端口
</span></span></span><span class="line"><span class="cl">        <span class="c1"># 接受 proxy_protocol，否则 log 中显示的链接源地址都是 unix:
</span></span></span><span class="line"><span class="cl">        <span class="kn">listen</span> <span class="s">unix:/dev/shm/nginx-example.sock</span> <span class="s">ssl</span> <span class="s">http2</span> <span class="s">proxy_protocol</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="kn">set_real_ip_from</span> <span class="s">unix:</span><span class="p">;</span>  <span class="c1"># 只对于来自 unix domain socket 的连接覆盖其源地址
</span></span></span><span class="line"><span class="cl">        <span class="kn">real_ip_header</span> <span class="s">proxy_protocol</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="kn">add_header</span> <span class="s">Alt-Svc</span> <span class="s">&#39;h3=&#34;:443&#34;</span><span class="p">;</span> <span class="kn">ma=86400&#39;</span><span class="p">;</span>  <span class="c1"># used to advertise the availability of HTTP/3
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1"># ...
</span></span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kn">server</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kn">server_name</span> <span class="s">foo.example.com</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1"># 可以多个域名共享 443/udp
</span></span></span><span class="line"><span class="cl">        <span class="kn">listen</span> <span class="mi">443</span> <span class="s">quic</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="kn">listen</span> <span class="s">[::]:443</span> <span class="s">quic</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="kn">listen</span> <span class="s">unix:/dev/shm/nginx-example-foo.sock</span> <span class="s">ssl</span> <span class="s">http2</span> <span class="s">proxy_protocol</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="kn">set_real_ip_from</span> <span class="s">unix:</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="kn">real_ip_header</span> <span class="s">proxy_protocol</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="kn">add_header</span> <span class="s">Alt-Svc</span> <span class="s">&#39;h3=&#34;:443&#34;</span><span class="p">;</span> <span class="kn">ma=86400&#39;</span><span class="p">;</span>  <span class="c1"># used to advertise the availability of HTTP/3
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1"># ...
</span></span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">stream</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># ...
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># 根据 TLS SNI 分流
</span></span></span><span class="line"><span class="cl">    <span class="kn">map</span> <span class="nv">$ssl_preread_server_name</span> <span class="nv">$name</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kn">example.com</span>             <span class="s">unix:/dev/shm/nginx-example.sock</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="kn">foo.example.com</span>         <span class="s">unix:/dev/shm/nginx-example-foo.sock</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="kn">learn.microsoft.com</span>     <span class="n">127.0.0.1</span><span class="p">:</span><span class="mi">8443</span><span class="p">;</span>  <span class="c1"># 用于 shadow-tls/xray-reality 等
</span></span></span><span class="line"><span class="cl">        <span class="kn">default</span>                 <span class="s">unix:/dev/shm/nginx-default.sock</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kn">server</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># 监听 443/tcp 并根据 SNI 分流
</span></span></span><span class="line"><span class="cl">        <span class="kn">listen</span> <span class="mi">443</span> <span class="s">reuseport</span> <span class="s">so_keepalive=on</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="kn">listen</span> <span class="s">[::]:443</span> <span class="s">reuseport</span> <span class="s">so_keepalive=on</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="kn">proxy_pass</span> <span class="nv">$name</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="kn">ssl_preread</span> <span class="no">on</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="kn">proxy_protocol</span> <span class="no">on</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="测试">测试</h2>
<p>目前 <code>curl</code>/<code>wget</code> mainline 还没有支持 QUIC，可以使用 <code>ymuski/curl-http3</code> 这个 docker 镜像：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ docker run -it --rm ymuski/curl-http3 curl https://static.monsoon-cs.moe/public/ --http3 -IL
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">HTTP/3 <span class="m">200</span>
</span></span><span class="line"><span class="cl">server: nginx/1.25.2
</span></span><span class="line"><span class="cl">date: Tue, <span class="m">26</span> Sep <span class="m">2023</span> 14:52:29 GMT
</span></span><span class="line"><span class="cl">content-type: text/html<span class="p">;</span> <span class="nv">charset</span><span class="o">=</span>utf-8
</span></span><span class="line"><span class="cl">strict-transport-security: max-age<span class="o">=</span><span class="m">63072000</span>
</span></span><span class="line"><span class="cl">alt-svc: <span class="nv">h3</span><span class="o">=</span><span class="s2">&#34;:443&#34;</span><span class="p">;</span> <span class="nv">ma</span><span class="o">=</span><span class="m">86400</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="参考">参考</h2>
<ul>
<li><a href="https://nginx.org/en/docs/stream/ngx_stream_ssl_preread_module.html">https://nginx.org/en/docs/stream/ngx_stream_ssl_preread_module.html</a></li>
<li><a href="https://nginx.org/en/docs/http/ngx_http_v3_module.html">https://nginx.org/en/docs/http/ngx_http_v3_module.html</a></li>
</ul>
]]></content:encoded></item><item><title>优化 MKL 在 AMD CPU 上的性能</title><link>https://monsoon-cs.moe/zh/2023-06-19-mkl-on-amd/</link><pubDate>Mon, 19 Jun 2023 00:00:00 +0000</pubDate><guid>https://monsoon-cs.moe/zh/2023-06-19-mkl-on-amd/</guid><description>&lt;h2 id="问题"&gt;问题&lt;/h2&gt;
&lt;p&gt;实验室有一些 AMD EPYC 7713 的服务器，采购的原因是组里有一些人的程序有非常高的 CPU 负载（我也不知道是什么负载，为什么不能跑在 GPU 上，我也没有精力去逐个帮助解决），框框多的 AMD 处理器非常适合这种需求。&lt;/p&gt;</description><content:encoded><![CDATA[<h2 id="问题">问题</h2>
<p>实验室有一些 AMD EPYC 7713 的服务器，采购的原因是组里有一些人的程序有非常高的 CPU 负载（我也不知道是什么负载，为什么不能跑在 GPU 上，我也没有精力去逐个帮助解决），框框多的 AMD 处理器非常适合这种需求。</p>
<p>不过 AMD 的处理器虽然香，用在炼丹实验室会有额外的问题：Anaconda 安装的 numpy 和 PyTorch 默认都使用了 MKL 作为 BLAS 的实现，MKL 的 library function 也是大部分高 CPU 负载程序的热点，但 <strong>MKL 会判断自己是否在 Intel CPU 上运行，如果不是，则没有优化效果。</strong></p>
<p>由于这是炼丹实验室，大家很少有足够的 HPC 基础去自己编译适合的 numpy 和 PyTorch 版本，也很难脱离 Anaconda，对于 MKL 的依赖因此很难去除。为此需要一个<strong>对一般用户无感知的解决方案</strong>。</p>
<h2 id="解决方案">解决方案</h2>
<p>通过搜索引擎可以搜索到一个广为流传解决方案：设置环境变量 <code>MKL_DEBUG_CPU_TYPE=5</code>。这是个曾经有效的解决方案，但<strong>对于 MKL 2020 及之后的版本不再有效</strong>。</p>
<p>最终我在<a href="https://documentation.sigma2.no/jobs/mkl.html">此处</a>找到了更巧妙的解决方案。</p>
<p>MKL 会调用一个 <code>mkl_serv_intel_cpu_true()</code> 函数以检查自己是否运行在 Intel CPU 上，只要提供一个虚假的、始终返回 <code>1</code> 的 <code>mkl_serv_intel_cpu_true()</code>，即可欺骗 MKL 让它认为自己在 Intel CPU 上运行。</p>
<p>为此，可以利用 Linux 的 <strong><code>LD_PRELOAD</code> 机制</strong>。<code>LD_PRELOAD</code> 指向的动态链接库有最高的加载优先级，只要编译一个想要的 <code>mkl_serv_intel_cpu_true()</code> 函数为 <code>so</code> 文件，并用 <code>LD_PRELOAD</code> 指向它，即可抢先完成此函数的加载。</p>
<blockquote>
<p>笔者也经常有耳闻 <code>LD_PRELOAD</code> 机制被用于库函数劫持攻击，此处算是一种妙用。</p>
</blockquote>
<h2 id="具体实施">具体实施</h2>
<p>新建 <code>mkl_trick.c</code>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="kt">int</span> <span class="nf">mkl_serv_intel_cpu_true</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>使用 <code>gcc -shared -fPIC -o libmkl_trick.so mkl_trick.c</code> 编译，并将生成的 <code>libmkl_trick.so</code> 复制到 <code>/usr/local/lib</code>。</p>
<p>在 Shell 的全局初始化文件中加入：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">MKL_DEBUG_CPU_TYPE</span><span class="o">=</span><span class="m">5</span>  <span class="c1"># 兼容旧版本 MKL</span>
</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">MKL_ENABLE_INSTRUCTIONS</span><span class="o">=</span>AVX2  <span class="c1"># 可选，指明 MKL 可以使用 AVX2</span>
</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">LD_PRELOAD</span><span class="o">=</span>/usr/local/lib/libmkl_trick.so
</span></span></code></pre></td></tr></table>
</div>
</div><p>实验室的同学有的用 Bash 也有的用 ZSH，所以两者都要修改：</p>
<ul>
<li>Bash: 新建文件 <code>/etc/profile.d/mkl.sh</code> 并添加上述内容</li>
<li>ZSH: 添加到 <code>/etc/zsh/zshenv</code></li>
</ul>
<h2 id="参考">参考</h2>
<ul>
<li><a href="https://documentation.sigma2.no/jobs/mkl.html">https://documentation.sigma2.no/jobs/mkl.html</a></li>
</ul>
]]></content:encoded></item></channel></rss>