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