最近算法同学反馈模型的显存太大了，batch size 打不上去，让我们看一下，所以有了这次排查。以下提到的模型信息和真实模型无关。排查的工具工欲善其事必先利其器，那么用什么东西来排查 PyTorch 的显存呢。PyTorch 内直接获取显存信息在 PyTorch 内我们可以通过 torch.cuda.memory_stats() 来查看当前的显存状态，一般来说我们会关注这几个指标。reserved_bytes.all.peak: 显存池中的最大显存allocated_bytes.all.peak：使用中的最大显存在 torch 中显存由显存池管理，如果删掉了一个 tensor 并不会直接释放它的显存，而是留在池子中等待再次分配。正因为此，torch 中 allocated 和 reserved 没有满的话并不代表没有显存压力，比如 80G 的显卡 reserved 了 75G，allocated 仅 60G，但还是可能会出现申请一个 6G 显存但是 reserved 池子中没有，只能调用 torch.cuda.empty_cache() 来清空缓存，这样会造成训练降速。PyTorch memory snapshot打印 reserved 和 allocated 还是不太直观，我们可以用 PyTorch 提供的另一个工具来 dump 显存以更精确的显存问题分析。torch.cuda.memory._record_memory_history() run_your_code() torch.cuda.memory._dump_snapshot("my_snapshot.pickle")打出这个 dump 文件之后，可以在 memory_viz 里查看文件内容，可以详细的看到每块显存是何时申请的。PyTorch TimelineTimeline 是我们常用的分析 torch 训练性能的工具，在 Timeline 中，除了算子的耗时，你也可以看到算子的显存使用情况。在 CPU Timeline 中，可以点击算子下面的小三角，即可在详细信息中看到显存的申请和释放情况。显存分析显存主要是两块，模型显存和激活值。FSDP 的模型显存分析首先看一下模型的情况，模型有 4 层，每层的参数量是 1B，总参数量约为 4B，也就是说只看参数的话应该是 16G 显存，参数、梯度和优化器状态应该是 16G + 16G + 32G 为 64 G。但是我们的模型使用了 FSDP2，除此之外还需要考虑 FSDP2 的作用。默认情况下 FSDP2 会对模型做全切分，包括参数，梯度和优化器状态，假设我们现在是 8 个 rank，参数、梯度和优化器状态应该在每一个 rank 上占用 8G，当然梯度并不是每一刻都存在，我们可以先不讨论梯度，只有参数和优化器状态之下每一个 rank 应该占用 6G。以上是在 fp32 下进行讨论，那如果我们配置了 FSDP2 的混合精度呢？我们把 param_dtype 和 reduce_dtype 都设置成 bf16 能节省显存吗？答案是并不会减少这 6G 的显存占用。FSDP2 的混合精度计算流程是这样的，如果正确配置了 submodules 和 forward prefetch，那么会在 layer N 计算时，对 layer N + 1 的参数做 All Gather，因为配置了 reduce_dtype=bf16，所以会先 cast 到 bf16 再通信，又因为配置了 param_dtype=bf16，所以通信完了之后获取到了参数就是 bf16 的。在 layer N 的前向计算完之后，FSDP2 的默认行为是释放掉这个 bf16 的完整参数。当反向时，layer N 反向时，会先 All Gather Layer N - 1 的参数，同时 Layer N + 1 的梯度已经计算好了也不会留着，而是 Reduce Scatter 到每一个 rank 上。最后做 Optimizer.step()，这里就不需要额外的通信和cast了，直接用切分的fp32权重和梯度做计算即可。所以在 FSDP 前向计算中，最多同时存在两个 layer 的 bf16 完整参数，所以此时的显存峰值应该是shard_param (fp32) + shard_optimizer (fp32) + 2 * layer_full_param (bf16) = 10G在反向计算中，最多同时存在 layer N + 1 的梯度，layer N 的参数和 layer N 的梯度，所以此时的显存峰值应该是shard_param (fp32) + shard_optimizer (fp32) + 2 * layer_full_grad (bf16) + layer_full_weight (bf16) = 12G这个图有点问题，没能体现出来分层的 all gather, reduce scatter，并且没有体现出来反向前还要做一次 all gather，凑合看吧激活值分析激活值则主要来自给模型输入的 batch。在大模型训练中，大模型的 batch 里可能是没有过 tokenizer 的原始语句或者是过完 tokenizer 的 id，进到模型之后用 nn.Embedding 去取 Embedding，所以它 batch 中单条样本通常不会多大。而推荐模型一般不会使用 nn.Embedding，而是使用一个外置的 Parameter Server，在数据流中去查表，我们称之为 pull_sparse，查出 Embedding 后给 Tensor 配置 requires_grad，然后放入 batch 中。反向结束之后需要从 Embedding Tensor 获取 grad，然后做 push sparse 把梯度返回给 Parameter Server。在看完前面说的 Memory Snapshot 之后，我确定问题就出在激活值这里。我们的框架做了 prefetch，正常情况下，我们会在前向开始时做上一步的 push_sparse，在后向开始时做下一步的 pull_sparse。所以在合理情况下，最多只会有三份 embedding 大小的显存，如下图所示。但实际的情况是什么呢？Step N - 1 的 batch 和 grad 一直到 step N 结束才被释放，也就是训练中最多会存在5份 embedding 大小的显存。原因分析现在我们看到了 Embedding 显存异常占用的表象，但是为什么会这样呢？为什么 batch 和 grad 没有能正常释放，这个问题困扰了我很久，我用 objgraph 发现找不到引用。最后发现这是一个 PyTorch 和 Python，框架和模型代码共同作用导致的问题。我们的训练代码大概是这样的while train_not_end(): data = next(dataset) outputs = train_one_step(data) process_outputs(outputs)模型代码是这样的def train_one_step(data): loss = model(data) optimizer.zero_grad() loss.backward() optimizer.step() return {"loss": loss} 乍一看应该就是普通的训练循环，但问题出在这里 return {"loss": loss} 只是返回 loss 为什么会造成显存泄露呢？这里有两个原因：PyTorch 中 loss 会引用整个计算图，所有带 backward fn 的参数和梯度都不会释放。Python 中循环变量不会在循环的结尾释放，而是在循环下一次运行到这里时，变量被覆盖才会释放。两者共同作用，导致配置了 requires_grad 的 Embedding Tensor 无法被释放。所以修复的方式也很简单不要直接返回 loss 变量，而是返回 loss.item()或者在训练循环的结尾删掉 data 和 output总结Python 里总有这种奇怪的小特性，这次算是被坑了一把。虽然病因很简单，但是 profile 显存，算显存理论值这块还是挺有趣的，也算是有些收获。ReferenceUnderstanding CUDA Memory UsageGetting Started with Fully Sharded Data Parallel (FSDP2)Autograd mechanics
需求最近尝(xian)试(de)把 nas 上的服务全部迁移到了 K3s 上，主要是觉得 Kubernetes 管理起来方便，而且 traefik 真的很好用，不需要我一个一个写 nginx conf。别的服务用起来都没有什么大问题，唯有用来下载的 qBittorrent，因为 K3s 默认不使用 IPv6，导致在下载的时候降低了很多速度。所以我尝试了在 K3s 上配置 IPv6。内网 IPv6 (ULA)肯定有人看到这一段会先骂为什么要在 IPv6 中使用 NAT，但是不要急，我们先从这里开始是为了保证在最极端的情况下（只有一个 /128）也能正常使用。重装 K3s根据 K3s 的文档，切换单栈/双栈网络只能在安装的时候配置，所以需要重装 K3s。在重装 K3s 时，我们使用如下环境变量，可以看到，我们为 cluster cidr 和 service cidr 分配不同的 IP 段，并且设置了 flannel-ipv6-masq，这个选项是用来开启 IPv6 的地址转换，默认情况是关闭的。export INSTALL_K3S_EXEC="--cluster-cidr=10.42.0.0/16,fd00::/64 --service-cidr=10.43.0.0/16,fd01::/112 --flannel-ipv6-masq"安装好之后我们创建一个容器，kubectl run -it --rm --image=alpine alpine，在容器中可以看到容器被分配了一个内网的 IPv6，并且可以使用宿主机 IP 访问 IPv6 外网。公网 IPv6 地址 (GUA)众所周知，IPv6 不应该有 NAT，明明 IP 多的可以给地球上每一颗沙子都分配 IP，为什么还需要 NAT 这种无奈之举呢？在家庭宽带中,运营商分给你的 IPv6 前缀不是一成不变的，和 IPv4 一样，可能重启光猫就会改变，而 K3s 目前是没有办法去修改 cluster cidr 的，和前面一样，是需要重装的。不过这个好像是 flannel 的锅，如果换成 calico 是可以解决的，但这个不在本文的讨论范围内。划分 IPv6 网段如果你对自家的 IPv6 前缀很有自信，可以手动划分一段 IP 给 K3s 使用，一般来说，ISP 提供的 IPv6 前缀一般是 /56 或者 /60，我这里是 /60，意味着有 16 个 /64 段可以用来划分。划分网段意味着你需要在路由器上填写静态路由表，如果你是用的路由器是 openwrt 的，可以在 网络 -> 静态路由 这里找到配置。比如我这里将 240e:xxx:xxx:e304::/63 分配给宿主机，可以划分两块 /64 给 K3s 使用，需要注意的是这里的网关需要填写的是宿主机对应网卡的 link-local 地址。但是如果你的路由器不支持直接填写静态路由表，可以尝试使用 DHCPv6-PD 的方式，看看路由器能不能直接分给你一段 IPv6 前缀并且自动修改好路由表，相关方式是使用命令 dhclient -d -6 -P eth0。重新安装 K3s，我们使用下面的环境变量。export INSTALL_K3S_EXEC="--cluster-cidr=10.42.0.0/16,240e:xxx:xxx:e304::/64 --service-cidr=10.43.0.0/16,240e:xxx:xxx:e305::/112"启动测试容器，可以看到我们拥有了一个 GUA Address，并且可以直接用来访问外网。NPT (IPv6 前缀转换)正如前面所说，家宽中的 IPv6 地址前缀经常变化，在家庭网络环境中，路由器除了会给你分配 GUA 地址之外，通常还会分配一个 ULA 地址，既然 IPv6 地址那么多，理论上我们可以实现 1:1 的 ULA 到 GLA 地址转换，而不用像 IPv4 那样，多个地址对应一个公网地址去做 NAT。这种方式被称作 NPT (Network Prefix Translation)。首先仍然是划分地址段，openwrt 在 br-lan 上分配的 ULA 地址段为 fd9b:d5c7:75f::1/60，那么我在这里把 fd9b:d5c7:75f:4::1/63 分配给宿主机。除此之外，我们还需要添加一条 default 路由，否则内网 ip 不会被转发，需要将接口设置成 WAN，目标设置成 ::/0, 网关这个需要使用命令行查看 pppoe-wan 接口的 peer 端的 link local。如果你不理解这是什么意思，可以直接输入 ip -6 route，默认应该有一条或者两条 default 路由，将 via 后面的地址抄上来即可。最后选择高级设置，将源地址设置成我们划分的子网。和上面类似，用分配的网络段重建 K3s。export INSTALL_K3S_EXEC="--cluster-cidr=10.42.0.0/16,fd9b:d5c7:75f:4::1/64 --service-cidr=10.43.0.0/16,fd9b:d5c7:75f:5::1/112"最后还需要添加一条 IPv6 的地址转换。# 如果你的 openwrt 版本 > 22.03, 那么应该已经默认使用 nftables 了 nft add rule inet fw4 srcnat oifname "pppoe-wan" ip6 saddr fd9b:d5c7:75f:4::/63 snat ip6 prefix to 240e:xxx:xxx:e304::/63 nft add rule inet fw4 dstnat iifname "pppoe-wan" ip6 daddr 240e:xxx:xxx:e304::/63 dnat ip6 prefix to fd9b:d5c7:75f:4::/63 # 不然的话应该使用 iptables ip6tables -t nat -A POSTROUTING -o pppoe-wan -j NETMAP --to 240e:xxx:xxx:e304::/63 -s fd9b:d5c7:75f:4::/63 ip6tables -t nat -A PREROUTING -i pppoe-wan -j NETMAP -d 240e:xxx:xxx:e304::/63 --to fd9b:d5c7:75f:4::/63可以看到我们内网 IPv6 地址的前缀被转成了公网的。不过这里还有一点小问题，当公网的 IPv6 地址变化时，我们需要手动的去调整 iptables/nftables，我看 openwrt 文档里给了一个 脚本，我看了一下还有一点小问题，如果要用的话最好自己固定 LAN Prefix，并且手动计算 WAN Prefix 的偏移。结语以上，就是我对家宽环境下 K3s 使用 IPv6 的探索，现在我的 qBittorrent 已经能正常使用 IPv6 了。
修复华硕笔记本在 Linux 上扬声器无声音的问题
最近因为装了台式，加上公司有发笔记本，我自己的笔记本就显得没什么用处了，正好可以用来装 Linux 玩。其实在去年6月刚买这台华硕灵耀 X13 (Zenbook S13 OLED) 的时候，我就尝试过装 Linux，那个时候整个 Ryzen 6000 系列在 Linux 上的键盘都是不可用的，直到两个月之后 Kernel 6.0 发布。时隔一年多再次尝试，这次遇到的问题倒是没有那么致命，只有指纹和扬声器不可用（插耳机可用），指纹是 Linux 的老大难问题了，一时半会也解决不了，而扬声器这个不仅是我这一台笔记本的问题，几乎所有华硕的笔记本都有，这里记录下如何修复。Cirrus SmartAMP第一次在这个笔记本上重装系统的时候我就注意到了，Windows 自动安装的声卡驱动并不能驱动扬声器，必须要到华硕的驱动页面下载一个 Cirrus SmartAMP 的驱动才行。这个 Cirrus SmartAMP 对应的设备是 CS35L41,是一个内置的音频放大器，跟应该跟华硕所谓的哈曼卡顿调音相关吧。这也是 Linux 扬声器没有声音的问题来源，从 dmesg 日志中我们可以看到$ dmesg | grep CSC [ 6.381872] Serial bus multi instantiate pseudo device driver CSC3551:00: Instantiated 2 I2C devices. [ 6.532659] cs35l41-hda i2c-CSC3551:00-cs35l41-hda.0: Error: ACPI _DSD Properties are missing for HID CSC3551. [ 6.532664] cs35l41-hda i2c-CSC3551:00-cs35l41-hda.0: error -EINVAL: Platform not supported [ 6.532696] cs35l41-hda: probe of i2c-CSC3551:00-cs35l41-hda.0 failed with error -22 [ 6.541464] cs35l41-hda i2c-CSC3551:00-cs35l41-hda.1: Error: ACPI _DSD Properties are missing for HID CSC3551. [ 6.541468] cs35l41-hda i2c-CSC3551:00-cs35l41-hda.1: error -EINVAL: Platform not supported [ 6.541503] cs35l41-hda: probe of i2c-CSC3551:00-cs35l41-hda.1 failed with error -22从上面的报错中可以看到，Linux 并不没有这个设备的驱动程序，而是缺少了这个设备在 ACPI Table 上的一些属性。这其实是华硕的锅，众所周知，ACPI Table 是硬件制造商提供的固件中包含的信息，华硕提供的固件里缺少了一些属性值，据说在 Windows 的驱动中，他们硬编码了相关属性值，而 Linux 这边没有这么做，为了让扬声器重新响起来，我们需要自己给 ACPI Table 打补丁。修复 ACPI TableDump DSDT Table我们这里用不到完整的 ACPI Table，只需要对 DSDT Table 进行操作即可$ sudo cat /sys/firmware/acpi/tables/DSDT > dsdt.dat $ iasl -d dsdt.dat之后会多出来一个 dsdt.dsl 文件，即反编译的 dsdt table。了解 CSC3551 在 DSDT 中的定义在 dsdt.dsl 中搜索 CSC3551，找到和它相关的部分以及它的上一级，在我的设备中，这部分是Scope (_SB.I2CA) { Device (SPKR) { Name (_HID, "CSC3551") // _HID: Hardware ID Name (_SUB, "10431F12") // _SUB: Subsystem ID Name (_UID, One) // _UID: Unique ID Method (_CRS, 0, NotSerialized) // _CRS: Current Resource Settings { Name (RBUF, ResourceTemplate () { I2cSerialBusV2 (0x0040, ControllerInitiated, 0x000F4240, AddressingMode7Bit, "\\_SB.I2CA", 0x00, ResourceConsumer, , Exclusive, ) I2cSerialBusV2 (0x0041, ControllerInitiated, 0x000F4240, AddressingMode7Bit, "\\_SB.I2CA", 0x00, ResourceConsumer, , Exclusive, ) GpioIo (Exclusive, PullDown, 0x0000, 0x0000, IoRestrictionOutputOnly, "\\_SB.GPIO", 0x00, ResourceConsumer, , ) { // Pin list 0x0004 }这里是要了解 CSC3551 这条设备挂在哪个总线下面，可以看到，我这个设备挂在 _SB.I2CA 这条线下，设备号是 SPKR，设备硬件ID是 10431F12。但是除了 I2C 总线，这个设备还可能挂载在 SPI 总线之下，下面是一个 SPI 总线下的 DSDT 部分示例Scope (_SB.PC00.SPI3) { Device (SPK1) { Name (_HID, "CSC3551") // _HID: Hardware ID Name (_SUB, "10431CAF") // _SUB: Subsystem ID Name (_UID, One) // _UID: Unique ID Method (_CRS, 0, NotSerialized) // _CRS: Current Resource Settings { Name (SBUF, ResourceTemplate () { SpiSerialBusV2 (0x0000, PolarityLow, FourWireMode, 0x08, ControllerInitiated, 0x003D0900, ClockPolarityLow, ClockPhaseFirst, "\\_SB.PC00.SPI3", 0x00, ResourceConsumer, , Exclusive, ) SpiSerialBusV2 (0x0001, PolarityLow, FourWireMode, 0x08, ControllerInitiated, 0x003D0900, ClockPolarityLow, ClockPhaseFirst, "\\_SB.PC00.SPI3", 0x00, ResourceConsumer, , Exclusive, )编写 Patch下面是示例 patchDefinitionBlock ("", "SSDT", 1, "CUSTOM", "CSC3551", 0x00000001) { External (<EXTERNAL>, DeviceObj) External (<EXTERNAL>.<SPK>, DeviceObj) Scope (<SCOPE>.<SPK>) { Name (_DSD, Package () // _DSD: Device-Specific Data { ToUUID ("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"), Package () { Package () { "cirrus,dev-index", Package () { <ADDRESS_PAIR> }}, Package () { "reset-gpios", Package () { <SPK>, Zero, Zero, Zero, <SPK>, Zero, Zero, Zero, } }, Package () { "spk-id-gpios", Package () { <SPK>, 0x02, Zero, Zero, <SPK>, 0x02, Zero, Zero, } }, Package () { "cirrus,speaker-position", Package () { Zero, One } }, // gpioX-func: 0 not used, 1 VPSK_SWITCH, 2: INTERRUPT, 3: SYNC Package () { "cirrus,gpio1-func", Package () { One, One } }, Package () { "cirrus,gpio2-func", Package () { 0x02, 0x02 } }, // boost-type: 0 internal, 1 external Package () { "cirrus,boost-type", Package () { One, One } }, }, }) } }其中<EXTERNAL> 一般为 _SB_<SPK> 为前面提到的哪条总线，如 I2CA<SCOPE> 和前面提到 Scope() 里的内容相同，注意这里的 _SB 少一个后置的下划线<ADDRESS_PAIR> 在 I2C 设备上是 0x0040, 0x0041，在 SPI 设备上是 Zero, One对于我这台笔记本来说，patch 如下DefinitionBlock ("", "SSDT", 1, "CUSTOM", "CSC3551", 0x000000001) { External (_SB_, DeviceObj) External (_SB_.I2CA, DeviceObj) Scope (_SB.I2CA.SPKR) { Name (_DSD, Package () { ToUUID ("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"), Package () { Package () { "cirrus,dev-index", Package () { 0x0040, 0x0041 }}, Package () { "reset-gpios", Package () { SPKR, Zero, Zero, Zero, SPKR, Zero, Zero, Zero, } }, Package () { "spk-id-gpios", Package () { SPKR, 0x02, Zero, Zero, SPKR, 0x02, Zero, Zero, } }, Package () { "cirrus,speaker-position", Package () { Zero, One } }, // gpioX-func: 0 not used, 1 VPSK_SWITCH, 2: INTERRUPT, 3: SYNC Package () { "cirrus,gpio1-func", Package () { One, One } }, Package () { "cirrus,gpio2-func", Package () { 0x02, 0x02 } }, // boost-type: 0 internal, 1 external Package () { "cirrus,boost-type", Package () { One, One } }, } }) } }编译与使用 patch使用命令编译 iasl -tc ssdt_csc3551.dsl， 获得文件 ssdt_csc3551.aml，将文件复制到 /boot 中在 /etc/grub.d/01_acpi 中写入，并 chmod +x /etc/grub.d/01_acpi 添加权限#!/bin/sh -e #sudo cp -f 01_acpi /etc/grub.d/01_acpi # Uncomment to load custom ACPI tables GRUB_CUSTOM_ACPI_DIR="/boot" # DON'T MODIFY ANYTHING BELOW THIS LINE! prefix=/usr exec_prefix=${prefix} libdir=${exec_prefix}/lib . "$pkgdatadir/grub-mkconfig_lib" # Load custom ACPI tables if [ x${GRUB_CUSTOM_ACPI_DIR} != x ] && [ -d ${GRUB_CUSTOM_ACPI_DIR} ]; then echo "Searching for custom ACPI tables in ${GRUB_CUSTOM_ACPI_DIR}" >&2 for file in ${GRUB_CUSTOM_ACPI_DIR}/ssdt*.aml; do if [ -f "${file}" ] && is_path_readable_by_grub "${file}"; then echo "Found custom ACPI table: ${file}" >&2 prepare_grub_to_access_device `${grub_probe} --target=device ${file}` | sed -e "s/^/ /" cat << EOF acpi (\$root)`make_system_path_relative_to_its_root ${file}` EOF fi done fi使用命令 grub2-mkconfig -o /boot/efi/EFI/fedora/grub.cfg 生成新的 grub 配置，并重启电脑。（这里的配置文件路径和发行版相关，如果是 debian 系的直接 update-grub2 就行）重启后测试扬声器可用，查看日志dmesg | grep CSC [ 0.004184] ACPI: SSDT 0x00000000A8AA53DE 000143 (v01 CUSTOM CSC3551 00000001 INTL 20220331) [ 6.393627] Serial bus multi instantiate pseudo device driver CSC3551:00: Instantiated 2 I2C devices. [ 6.585168] cs35l41-hda i2c-CSC3551:00-cs35l41-hda.0: Cirrus Logic CS35L41 (35a40), Revision: B2 [ 6.585683] cs35l41-hda i2c-CSC3551:00-cs35l41-hda.1: Reset line busy, assuming shared reset [ 6.642721] cs35l41-hda i2c-CSC3551:00-cs35l41-hda.1: Cirrus Logic CS35L41 (35a40), Revision: B2 [ 6.754917] cs35l41-hda i2c-CSC3551:00-cs35l41-hda.0: DSP1: Firmware version: 3 [ 6.754927] cs35l41-hda i2c-CSC3551:00-cs35l41-hda.0: DSP1: cirrus/cs35l41-dsp1-spk-prot-10431f12.wmfw: Fri 27 Aug 2021 14:58:19 W. Europe Daylight Time [ 7.213773] cs35l41-hda i2c-CSC3551:00-cs35l41-hda.0: DSP1: Firmware: 400a4 vendor: 0x2 v0.43.1, 2 algorithms [ 7.214860] cs35l41-hda i2c-CSC3551:00-cs35l41-hda.0: DSP1: 0: ID cd v29.63.1 XM@94 YM@e [ 7.214868] cs35l41-hda i2c-CSC3551:00-cs35l41-hda.0: DSP1: 1: ID f20b v0.1.0 XM@176 YM@0 [ 7.214876] cs35l41-hda i2c-CSC3551:00-cs35l41-hda.0: DSP1: spk-prot: C:\Users\tyang\Desktop\Product Setting\SmartAMP\ASUS\ASUS_Zenbook\UM5302\Tuning_release\220210\UM5302_L_with Rtrace_Tuning_YC_Rattle.bin [ 7.299115] snd_hda_codec_realtek hdaudioC1D0: bound i2c-CSC3551:00-cs35l41-hda.0 (ops cs35l41_hda_comp_ops [snd_hda_scodec_cs35l41]) [ 7.302803] cs35l41-hda i2c-CSC3551:00-cs35l41-hda.1: DSP1: Firmware version: 3 [ 7.302809] cs35l41-hda i2c-CSC3551:00-cs35l41-hda.1: DSP1: cirrus/cs35l41-dsp1-spk-prot-10431f12.wmfw: Fri 27 Aug 2021 14:58:19 W. Europe Daylight Time [ 7.761640] cs35l41-hda i2c-CSC3551:00-cs35l41-hda.1: DSP1: Firmware: 400a4 vendor: 0x2 v0.43.1, 2 algorithms [ 7.762715] cs35l41-hda i2c-CSC3551:00-cs35l41-hda.1: DSP1: 0: ID cd v29.63.1 XM@94 YM@e [ 7.762724] cs35l41-hda i2c-CSC3551:00-cs35l41-hda.1: DSP1: 1: ID f20b v0.1.0 XM@176 YM@0 [ 7.762731] cs35l41-hda i2c-CSC3551:00-cs35l41-hda.1: DSP1: spk-prot: C:\Users\tyang\Desktop\Product Setting\SmartAMP\ASUS\ASUS_Zenbook\UM5302\Tuning_release\220210\UM5302_R_Tuning_YC_Rattle.bin [ 7.846106] snd_hda_codec_realtek hdaudioC1D0: bound i2c-CSC3551:00-cs35l41-hda.1 (ops cs35l41_hda_comp_ops [snd_hda_scodec_cs35l41])(可选) 下载 cirrus 固件如果你的发行版没有提供 cirrus 驱动的固件，那你需要手动下载git clone --depth=1 https://gitlab.com/asus-linux/firmware.git cp -r firmware/cirrus /lib/firmware(可选) 内核版本华硕这个不仅需要 dsdt, 固件，还要有一个内核的补丁，这里我的建议是升到最新的内核，如果没有的话开摆等着别人修吧。ReferenceAsus Notebook Linuxthor2002ro/asus_zenbook_ux3402za
自从买了华硕灵耀 X13 (aka Asus Zenbook S 13 OLED) 这台破笔记本之后，我一直被它的休眠问题所折磨，包括但不仅限于：插着电会自动重启，而且是 Reset 那种，Windows 日志里也看不到是怎么挂的；休眠后睡不醒，只能强制重启；关掉休眠功能之后，CPU 跑满，风扇狂转榨干电池。忍受了大半年之后，我才意识到一切的罪魁祸首都是啥笔微软在推的 S0 Sleep (Modern Standby)。但是这台笔记本的 BIOS 里并没有设置回到 S3 Sleep 的选项，要如何实现呢？UMAF (Universal AMD Form Browser)如果你搜索和锐龙的 S3 睡眠相关问题，多半会搜索到让你用 UMAF 这个工具的。这个工具的原理是修改 UEFI Variable 中 AMD_PBS_SETUP，来打开 S3，同样原理的还有 RU.EFI(http://ruexe.blogspot.com/)。但是这种方法在这台笔记本上并不可用，修改完了之后重启，Windows 会无法启动，蓝屏报错 ACPI_TABLE_ERROR，我推测是 UMAF 所用的结构体不能和 6000 系完全兼容，或者单纯不能和这台笔记本兼容，导致修改的位置有错。不过我也不敢用 RU.EFI 一个个去试，所以还是算了吧。覆写 DSDT Table尽管 UMAF 这条路走不通，但不代表我们无路可走。影响 Windows 休眠设置的主要是 ACPI Table 的 DSDT 部分，如果我们直接覆写这一部分呢？虽然 Windows 没有这样的功能，但是玩黑果对这个倒是很熟，黑果的 Bootloader: Clover （虽然现在都是用什么 OpenCore 了） 就提供了这样的功能。关闭 S0 睡眠在管理员权限的 CMD 中输入reg add HKLM\System\CurrentControlSet\Control\Power /v PlatformAoAcOverride /t REG_DWORD /d 0重启电脑之后，输入powercfg /a可以看到 S0 的睡眠状态是不可用的。提取 DSDT首先下载这个 iASL Compiler and Windows ACPI tools，解压之后进入这个文件夹，打开 cmd 或者 powershell。.\acpidump.exe -b -n DSDT -z .\iasl.exe -d .\dsdt.dat提出出来文件 dsdt.dat，并反编译成 dsdt.dsl。虽然我看别人的文章里说的要在 Clover 里提取 DSDT，否则会出现无法识别电池的问题，但是我自己都试了一遍，发现提取出来的是一样的。修改 DSDT用 notepad 或者其他文本编辑器打开 dsdt.dsl，只需要修改两处DefinitionBlock ("", "DSDT", 2, "_ASUS_", "Notebook", 0x0107200A)这一行最后的数字，要设置的大一点，比如 0x01072010，这样 Windows 才会加载新的 dsdt。 Name (XS3, Package (0x04) { 0x03, Zero, Zero, Zero })把 XS3 改成 _S3。保存文件并重新编译iasl.exe dsdt.dsl编译出来文件 dsdt.aml安装 Clover首先要挂载 EFI 分区diskpart sel disk 0 sel part 1 ass letter=X这里的 disk 0 和 part 1 要视情况而定，请用 list disk 和 list part 确定你电脑上具体哪个是 EFI 分区。从 这里 下载 Clover 的最新版本。mkdir X:\EFI\CLOVER\ACPI\Windows copy CLOVERX64.efi X:\EFI\CLOVER copy dsdt.aml X:\EFI\CLOVER\Windows\ notepad X:\EFI\CLOVER\config.plist在 config.plist 文件中输入<?xml version="1.0" encoding="UTF-8"?> <plist version="1.0"> <dict> <key>Boot</key> <dict> <key>Timeout</key> <integer>-1</integer> <key>Fast</key> <false/> </dict> <key>GUI</key> <dict> <key>Scan</key> <dict> <key>Entries</key> <true/> <key>Legacy</key> <false/> </dict> </dict> </dict> </plist>重启电脑并进入 BIOS关闭安全启动与设置启动项关闭安全启动这个自己看 BIOS 选项，不过要说的一点是，关闭安全启动之后，BitLocker 加密的硬盘将无法自动解密，有使用 BitLocker 的建议提前关闭。设置启动项即将 CLOVERX64.EFI 添加到启动项中并设置默认启动。保存设置，重启电脑。重启之后再查看 powercfg /a，可以发现 S3 睡眠已经是可用的状态了。一些问题休眠恢复时蓝屏AMD 有一个设备，AMD Audio CoProcessor，看字面意思是音频协处理器，它没有什么用处，但是会导致睡醒的时候蓝屏，所以在设备管理器里禁用这个就好。无法使用USB键鼠唤醒是有这个问题，但是没有看到解决方案，就当是小小的遗憾吧，总比电脑天天随机重启好。引用S3 sleep on Windows for ASUS ROG FlowTurn on S3 Sleep using UMAF
在用 CR660X 的时候遇到一个艰难的问题，所有的 openwrt 的无线表现都很一般，宿舍千兆网最多只能跑到 500M，哪怕是用所谓的超频固件，贴在路由器边上测都不行。性能表现最好的，反而一个非常老的 Padavan 固件，版本号为 4.4.3.9-100_f01e96c，编译时间是 2021.06，能跑到 850M，几乎赶上用网线的速度了。但是，UU 加速器不支持 Padavan，没有它的话，垃圾教育网下裸连 OW2 服务器，延迟高就不说了，还丢包严重，经常看到人乱飞。啊？为什么不打国服，我也想啊，网易你倒是让我在 Xbox 上玩国服啊。所以还是要想办法装上 UU。首先 UU 的安装脚本是，http://uu.gdl.netease.com/uuplugin-script/202012111056/install.sh简单观察一下脚本的运行机制，就是去下载对应系统的 uuplugin_monitor.sh，然后运行这个脚本去下载 uuplugin 也就是实际运行的程序，这个是静态编译的程序，可能它唯一的依赖就是 kmod-tun，这个就要看你编译内核的时候有没有选了，我看我这个 padavan 是选了的。如果直接用 openwrt 的参数来安装，即 bash install.sh openwrt $(uname -m)，会出现各种问题，主要是：Padavan 默认根目录不可写，需要把安装路径改到 /tmp，同时也要修改 monitor.sh 的Padavan 没有 rc 目录，config_bootup 不可用。Padavan 默认的 lan 接口是 br0 而不是 br-lan综上，做 install.sh 脚本做的修改为3d2 < exec 2>/tmp/install.log || true 57c56 < INSTALL_DIR="/usr/sbin/uu/" --- > INSTALL_DIR="/tmp/uu/" 144c143 < --- > sed -i 's/\/usr\/sbin/\/tmp/g' ${MONITOR_FILE} 345c344 < interface="br-lan" --- > interface="br0" 386,387c385,386 < config_bootup < [ "$?" != "0" ] && return 8 --- > # config_bootup > # [ "$?" != "0" ] && return 8或者下载我 patch 好的 https://pastebin.com/csiKCnkM跑完之后就可以在手机端 App 添加设备了。
返校的时候我想 NAS 太重了，懒得带了，于是把硬盘拆了出来，准备接到 Rock5 上用，然而一接上我傻了。。。屁用没有的官方文档如果你直接在网上搜索文档的话，多半会搜到这一篇 当 Synology NAS 出现故障时，如何使用计算机恢复数据？，绝大部分博客什么的都是照抄官网的这一篇。然而当你真正去使用的时候，才会发现这篇屁用没有。别说 lvm 了，你连这个硬盘上连个分区都见不到，谈何挂载。修复分区表在这之前，推荐先备份硬盘前 10M 的空间，防止以后还想用，我就忘了备份了，不知道后面群晖还能不能用😭。dd if=/dev/sda of=backup.img bs=1M count=10然后安装并使用 testdisksudo apt install testdisk sudo testdisk /dev/sdb一定要选 EFI 格式，Analyse -> Quick Search，只需要保留最后一个分区，按 Enter -> Write，然后重启。挂载 LVM现在分区出来了，一定可以上面了那个文章了吧，然而还是不行。用上面文章的给的 mdadm -AsfR && vgchange -ay 会在 dmesg 里报一个 missing superblock，要用mdadm --assemble /dev/md0 /dev/sda1 --update=devicesize --verbose vgchange -ay似乎会自动补上 superblock，之后再用就不需要了。挂载 BTRFS我为什么不用 ext4 呢鬼知道群晖这个 btrfs 实现里加了什么魔法，直接挂载会报一个invalid root flags, have 0x400000000 expect mask 0x1000000000001这是内核的一个补丁导致的 [btrfs: tree-checker: Add ROOT_ITEM check](https://github.com/torvalds/linux/commit/259ee7754b6793af8bdd77f9ca818bc41cfe9541)要么重新编译内核， patch 掉这段，除了这段还有别的要改得，但是考虑到数据安全，我不敢动了。要么像我一样，装个有旧内核的虚拟机。对于 ubuntu 来说，没有引入这个补丁的最后一个版本是 4.15.0-108，可以直接安装 ubuntu 18.04.4 来解决。但不管怎么解决，都还是很恶心人，之后找个机会迁移到 True NAS 算了。
RK3588 上手体验千等万等，RK 3588 终于到手了，虽然它是准预售版本，虽然认识的大佬两个月前就拿到了开发者版本，但是不影响我现在开始折腾。系统安装写这篇文章的时候，Rock 5B 或者说 RK 3588 还没进到内核。最大的问题来自 GPU 驱动，Rockchip 不舍得花钱买 Mali G610 的 GPU 驱动，还得等大佬逆向，而且这个时候那个大佬还在做 M1 的 GPU 驱动，所以还要等相当长的一段时间。所以这个时候就只能 Rockchip 提供的 5.10 内核。Radxa ( Rock5 的制造商）提供了 Debian 和 Ubuntu 的 Build，但是不太好用，尤其是那个 Debian Desktop 2022.09.01，有 Bug 会让启动桌面的时候整个板子卡死。推荐使用 armbian/build 自己构建一份，armbian 的默认配置比 radxa 丰富些，比如开了 kvm 和一堆网卡驱动。不过需要注意的是，写文章的时候，armbian 的 rock5 仍然是 wip 状态，需要点击下面的 SHOW WIP 才能出来。使用 armbian 会有其他的问题，比如 rga，比如不能用 spi boot，所以推荐用 radxa 的，自己修改内核。NVME 启动没想到连 arm 板子都可以用 nvme 并且从中启动了，需要先把 uboot 写到 spiflash 里，参考官方 Wiki。但是这个 uboot 不能启动 armbian 的，也不知道是这个 uboot 的问题，还是 armbian 的 uboot.scr 的问题。性能测试首先当然跑个分看看，这里我就不和 RK 3399 比了，直接和我的 NAS 上 J4125 比较：RK 3588J41257z compress1665/122781886/61747z decompress2363/156262379/7728Geekbench 5559/2374460/1488不跑太专业的了，这点娱乐跑分已经可以说明问题了，单核和 J4125 其实差的不多，但是 RK 3588 胜在它有 4xA72 + 4xA53。而且 RK 3588 由于制程的进步，在发热量上比 3399 有了巨大的进步。用同款散热器，不打开风扇，3399 0 负载下能有 45 度，3588 只有 37 度。满负载打开风扇 3588 也只有 45 度，3399 快 50 度了。桌面使用首先游戏是不太现实，GPU 的驱动是烂的。虽然有一个用户空间的驱动 libmail，但是很少有程序能用，除了 rk 给的那个 glmark-es，包括桌面本身都是跑在 llvm-pipe 下的 (CPU 渲染)，这样还能输出 4k@60，我已经很感动了。因为桌面也是 CPU 渲染的，导致在进行复杂操作，比如播视频的时候也会大量占用 CPU。在 gnome 这种特效很高的 DE 下，动画效果非常卡顿，就算是 XFCE 这种，在拖动窗口的时候也会有撕裂。总的来说，暂时不适合作为桌面使用。视频编解码视频编解码才是 RK 3588 的重点，但是宣传的很美好，实际的使用体验并没有那么美好。修改 udev在这之前，要修改 udev 配置sudo -i echo 'KERNEL=="mpp_service", MODE="0666", GROUP="video"' > /etc/udev/rules.d/50-mpp.rules echo 'KERNEL=="rga", MODE="0666", GROUP="video"' > /etc/udev/rules.d/50-rga.rules udevadm control --reload && udevadm trigger adduser "你的用户名" video这么做的原因是 rockchip 是从 3399 的 rootfs 直接改过来的，3399 里处理编码的模块是 /dev/vpu-service ，但是在这里应该是 /dev/mpp_service。还有一个 /dev/rga 这个不是直接参与编码的，但是当视频格式不满足 mpp 的输入时，比如长宽不满足 16 的倍数，视频格式不是 NV12 时，都会调用这个做一点转换。ffmpeg首先是大家最熟悉的 ffmpeg，先说结论：不能用rockchip 之前给 ffmpeg 提交过一些代码，让它可以支持 rkmpp (rockchip 的视频编解码模块)，但后来因为协议问题，就搁置了。到现在，ffmpeg 也只能调用解码器，而不能编码，而且编码器还是坏的 😅 。但还是说下怎么用吧，正常发行版的 ffmpeg 都不带 rkmpp 支持，要自己下载，如果添加了 radxa 的源，可以直接 apt install librockchip-mpp-dev，如果没有的话，则要自己编译 mpp然后重新编译 ffmpeg./configure --enable-version3 \ --enable-drm \ --enable-rkmpp \ --enable-libx264 \ --enable-libx264ffmpeg 硬解ffmpeg -benchmark -c:v h264_rkmpp -i test.mp4 -f null -然后就会收获一堆报错。gstreamer前面有说到 rockchip 因为协议的问题，不再做 ffmpeg 的适配，而是将重心转向了协议较为宽松的 gstreamer。添加了 radxa 源的，可以从源里下载 gstreamer 以及 mpp 的插件 gstreamer1.0-rockchip1，或者可以自己编译 gstreamer-rockchip如何确认自己能使用 mpp 的插件？gst-inspect-1.0 |grep mpp typefindfunctions: audio/x-musepack: mpc, mpp, mp+ rockchipmpp: mppjpegdec: Rockchip's MPP JPEG image decoder rockchipmpp: mppvideodec: Rockchip's MPP video decoder rockchipmpp: mppjpegenc: Rockchip Mpp JPEG Encoder rockchipmpp: mppvp8enc: Rockchip Mpp VP8 Encoder rockchipmpp: mpph265enc: Rockchip Mpp H265 Encoder rockchipmpp: mpph264enc: Rockchip Mpp H264 Encoder如何使用 gstreamer 编解码解码gst-launch-1.0 filesrc location=test.mp4 ! qtdemux ! queue ! h264parse ! mppvideodec ! videoconvert ! autovideosink值得吐槽的是，虽然宣传的时候说支持 av1 硬解，但是 gstreamer 里并没有适配，mpp 的代码倒是写好了，需要的话只能自己去调用 mpp编码gst-launch-1.0 videotestsrc ! mpph265enc ! filesink location=test.mp4HDMI IN4k 输入录屏也是 3588 的宣传重点之一，那它能不能用呢，也是我非常期待的一部分，原本是打算将 3588 做成 Xbox/Switch 的串流器用的，那它能不能用呢？部分能用查看 HDMI IN 信息这是 switch 接上去的输出$ v4l2-ctl -d 0 -V Format Video Capture Multiplanar: Width/Height : 1920/1080 Pixel Format : 'BGR3' (24-bit BGR 8-8-8) Field : None Number of planes : 1 Flags : premultiplied-alpha, 0x000000fe Colorspace : Unknown (0x00000018) Transfer Function : Unknown (0x00000040) YCbCr/HSV Encoding: Unknown (0x000000ff) Quantization : Default Plane 0 : Bytes per Line : 5760 Size Image : 6220800这是 xbox 接上去的输出$ v4l2-ctl -d 0 -V Format Video Capture Multiplanar: Width/Height : 3840/2160 Pixel Format : 'NV12' (Y/CbCr 4:2:0) Field : None Number of planes : 1 Flags : premultiplied-alpha, 0x000000fe Colorspace : Unknown (0x00000018) Transfer Function : Unknown (0x00000040) YCbCr/HSV Encoding: Unknown (0x000000ff) Quantization : Default Plane 0 : Bytes per Line : 3840 Size Image : 12441600要吐槽的是，虽然它这个接口是 HDMI 2.0，理应支持 4k@60 fps yuv444，但是这里却是 420。应该是可以靠改 edid 来实现，但是我没有研究怎么改。不过无妨，因为编码器只能输入 NV12 即 420。显示抓取的画面使用 v4l2v4l2-ctl -d 0 --set-fmt-video=width=3840,height=2160,pixelformat=NV12 --stream-poll --stream-to=- | ffplay -f rawvideo -video_size 3840x2160 -pixel_format nv12 -然后会发现达不到 60fps 并且 CPU 占用巨大，理论上 rawvideo 应该不用解码，不应该会有 cpu 占用，但是因为桌面是 cpu 渲染的，播放 4k 视频的时候会大量占用 cpu 用在渲染桌面上，并且帧率还达不到 60fps。使用 gstreamergst-launch-1.0 v4l2src device=/dev/video0 \ ! video/x-raw,width=3840,height=2160,format=NV12,framerate=60/1 \ ! queue ! xvimagesink同理，达不到 60fps。这个桌面渲染的问题只能等到 gpu 驱动修好了才能用了编码和串流抓取的画面虽然不能直接回显，但是我能录屏和串流啊，，，能吧？不太行gst-launch-1.0 v4l2src device=/dev/video0 \ ! video/x-raw,width=3840,height=2160,format=NV12,framerate=60/1 \ ! queue ! mpph265enc ! filesink location=test.mp4然后就可以用 mpv 播放了，为什么说它不太行呢，因为有个大 bug —— mpp 编出来的没有关键帧，意味着你没有办法拖动视频，所有的操作都会让你回到开头。这也导致了另一个问题 —— 不能串流。本来是打算用 hls 做串流的，正好 gstreamer 也有 hlssink，但是这个是依赖关键帧做视频切割的，没有关键帧它就不会切了，所以不行。但是我看 mpp 的代码里是有关键帧的相关的内容，mpph265enc 的参数里也有 header-mode 这个看起来相关的东西，但是实测依然不能实现，这就没办法了。我打算后期尝试一下自己调用 mpp 编码看看。总结硬件上趋于完善，软件上急需改进硬件上挑不出来什么毛病，各种外设也很齐全。但是软件上的小毛病实在是太多了，导致完全做不到他宣传的效果。期待一波进主线内核，至少 GPU 驱动要给我修好吧。还有 EDK2 如果能做好的话，那就可以想装什么发行版装什么了，和 PC 没有什么区别了。未来可期
作为一个长年白嫖各种盗版的人，终于在实习工作之后认识到该买一些正版以支持作者了（其实主要原因是最近看盗版的体验太差了）。但是在哪里买正版，怎么买正版仍然是一个问题，所以这次就对国内外各种我使用过的网站做一点点总结。B站漫画人在大陆，想看正版二次元最简单的方式肯定就是给叔叔打钱。但是众所周知，叔叔家是不可能让你看色色的东西的，所以在叔叔家花钱算的上是正版的受害者。但是如果你想第一时间看上连载的话，叔叔家是你唯一的选择。对岸和日本都是有杂志的，他们的电子版通常只会卖单行本，而对岸的单行本通常比日本慢上几个月，电子版的单行本比上纸质版的又要慢上一点，最终的结果就是台版的电子单行本往往比连载慢了大半年。当然你有能力看日语也可以去买日文杂志的原版。IP 限制：大陆购买方式：微信支付宝银联DRM: 有B 漫的 DRM 已经被逆完了，因为受不了那些 python 写的单线程 request 的龟速，我也用 Go 写了一个下载器 bilimanga-downloader。东立电子书城东立是台湾的一家漫画出版商，跟集英社，讲谈社都有合作。但是很多作品他不出电子版的连载，只能买单行本，比如，《出租女友》 aka 《租借女友》。还有的他压根不出电子版，比如 《戀上換裝人偶》aka 《更衣人偶坠入爱河》。另外非东立出版的一般也不会有，比如青文出版社的 《明日同学的水手服》。IP 限制：港澳台购买方式：支持国内发的Visa （我只有一张 Visa 卡可以测试，一般来说 MasterCard 也是支持的）DRM: 无虽然没有做加密，他也没有给直链下载，不过他的接口还是比较弱智的，非常容易就能爬下来。# Book group info, book id in here curl -H "Authorization: Bearer {your_token}" "https://api.tongli.tw/UserBook/BooksList?bookGroupID={book_group_id}&sortType=5&pagesize=9999&page=1" # book info, page url in here curl -H "Authorization: Bearer {your_token}" https://api.tongli.tw/Comic/sas/{book_id}Readmoo 读墨读墨是一家台湾的电子书商城。IP 限制：有限制购买方式: 支持国内发行的VisaDRM: 有Readmoo 是几家台湾卖书平台里，少有的能让我正常购买的，其他几家，我不挂梯子不能购买，我挂着台湾梯子网页都打不开了，也不知道为什么。Readmoo 的 DRM 做的挺牛，但是在本地的解密接口却是完全开放的，找到下载文件的路径，对着请求 http://localhost:9257/ 就能拿到解密后的文件。# 建议在 git bash 里跑，因为要 find 和 curl cd ~\AppData\Romaing\Readmoo\downloads find . -type d -exec mkdir ~/Downloads/{} \; find . -type f -exec curl http://localhost:9257/{} -o ~/Downloads/{} \;但是他们家的 epub，即使解密了格式也有问题。比如我常用的多看，打开没有字，还有一些阅读器直接显示文档损坏，但是🍎的阅读器，Chrome 的 Epub 插件，静读天下等等不少阅读还是可以正常读。KoboKobo 是一个比较国际化的电子书平台了，欧美，日本，台湾，香港都有市场。IP 限制: 能翻墙就行DRM: 有支付方式: 支持国内发行的VisaKobo 算的上是 Amazon Kindle 的竞品了，书还是挺全的，也在努力推自家的阅读器。但是它也逃不过 DRM 的恶习，Kobo 的 DRM 要配合 Calibre 解，在 Calibre 里安装两个插件，ascm-calibre-plugin, DeDRM_tools。其中 acsm 在 Calibre 的在线插件列表就能找到，而 DeDRM_tools 要在 github 下载安装。安装完成后，点开插件设置，双击 acsm 的插件，可以登录一个 ADE 的账户，也可以建一个匿名账户，这个不影响。然后从 Kobo 网页版下载书籍的 acsm 文件，导入到 Calibre 里即可转换成 Epub。Kobo 的好处是它转换后能较好的保留 Epub 的正常文件结构，这个比 Readmoo 好一点。但是看漫画来说，我还是倾向于不打包，直接zip或者文件夹丢给 Perfect Viewer 都能看。反倒是 Epub 会看起来很难受。Book WalkerIP限制：日本购买方式：不局限于信用卡，支持海外主流的各种支付方式，支持国内发行的 VisaDRM：有妈的这家网页和APP做的难看不说，DRM 倒真是有一手。没有比较正常的解 DRM 方式，因为网页版它使用 Canvas 绘图，App 下的是加密的压缩包。这算是我现在遇到最绝的一个了。当然也不是没有下载的办法，见 Manga_downloader。但是就我买的商品，《明日同学的水手服 画册》，我和 U2 大佬发的扫图版比对了一下，发现不仅清晰度不如人家扫图的，甚至连色彩都对不上，不知道是这一家的问题还是这个电子版的问题，我就不深究了，不会在这家买第二次。强烈不推荐。DMM FanzaFanza 上卖很多同人漫画，通常是本子之类的。IP限制：限制日本，甚至会限制一些 IDC购买方式：非常麻烦，非日本发行的信用卡都不给用，只能先找代购买点数，通常是 ￥1000 起步，总之很不灵活 我用国内发行的 AMEX 卡是可以直接购买的DRM: 看商品，反正我买的本子是没有。三个月不用的点数还会过期，你没事吧。 (原来是赠送的点数会过期，我没事吧)DL SiteDL Site 地位和 Fanza 差不多吧IP限制: 无限制购买方式: 支持国内发行的VisaDRM: 看商品DL Site 对于非日本人来说比较友好一点，没有那么严格的 IP 限制，还有汉化的网页。但是他和 Fanza 的区别还是在于创作者的选择，比如八寻老师的作品就只有 DMM，没有 DL Site。LezhinLezhin 是一个看韩漫 Webtoon 的平台IP 限制: 部分地区无法开启成人模式购买方式: 支持国内发行的 VisaDRM: 无为什么我会看韩漫呢？其实我只是想看 Crazy Alice 的 一个变态的日常生活这个网站的 api 我没看出来，但是它是没有 DRM 的，点开一个漫画，从头拉到尾，然后用 Save All Resources 这个插件全部保存下来就行了。首充有双倍点数，建议用完就重开。总结对于中文作品来说，漫画追连载只能B站，单行本首选东立，其次是读墨。小说首选 Kobo。
一转眼又到年末了，看了那么多平台替我写的总结，突发奇想要自己写一下追番总结，回顾一下我今年都看了哪些冻鳗，不一定是追番，也有因为某 UP 的新番时光机而追的老番。不过我是到了年末才想起来，总有些被我忘记的。1月Beastars 动物狂想曲 第一季/第二季第一季应该是在火车上看完的，当时看完感觉太潮辣。但是第一季帅是让雷狗子帅完了，最开始的杀人（羊）案还没解决呢。第二季讲的就是这个事情，第二季的故事，怎么说呢，一言难尽。在展现人物心理上，真的做的很好，鹿在狮子组中霸道与恐惧，狮子面对鹿的不解，不屑但又敬佩，熊在回想羊时的自我催眠，以及主角狼的成长。但是结局真的迷惑住了，怎么看我都看不懂😅 看了一下评论区大概是因为删减的厉害，有机会去看看漫画。Beastars 算不算 Furry 呢堀与宫村这么多年看过进展最快的恋爱番，别的番几季完成不了的事情，这里只要12集。说是这么说，但是我觉得观感还是很好的，尽管我看到空间有人说进展太快了显得不负责任(?)，那毕竟动漫里的时间不能按现实中来看。看到一半我去补了漫画，动漫其实删减了很多，不过总体上还算合理吧。想娶Miyamura悠哉日常大王 1|2|3本来以为点兔3这个周指活完结了我就要死了，没想到一月接着就来了喵帕斯3。因为 B 站没有喵帕斯前两部的版权，所以前面的我也没有看过。本来觉得萌豚日常番看不看前作影响不大，直到某一集里提到莲酱和一个朋友的离别，第一部她哭的好惨，这一部她却忍下来了，由此看出来喵帕斯的成长，到这里我才想起来去看前作。剧情啥样，我想不起来了4月Megalo Box第一次看这番的时候还是和我高中室友，周六中午偷偷玩学校电脑，一起看的，当时我只觉得这番画风好神奇，并没有看下去。看到这个要出第二季了，我迅速补完了第一季，真是太爽了，男孩子就应该看这样的番。第二季的风格相较于第一季沉重多了，但是知晓原因之后又觉得有点不合理，尤其是辛夫的塑造真的太过了，仿佛跟 Joe 有什么杀父之仇一样。看到结局我是错愕的，正如剧中活在 Joe 给的梦里的人一样，期许着 Joe 燃尽生命，获得奇迹般胜利的观众，又何尝不是活在 Joe 给的梦里呢？写到这里我又想起了高中室友，是个个性有点奇怪，但是其实待人很好的一个人。不过他毕业之后删掉了所有人的好友和联系方式，属于是特立独行了。我为什么还挺想他的。影宅又是一部结局原创烂尾番，又是一部我看一半去看漫画的番，我只想讲，这番到10集就完结了，这10集可以打满分，后面千万别看。PA 求你不要再惦记你那原创了86 不存在的战区86 不存在的影宅，先审后播制度的实施，让这两个番完全没机会上架国内平台，可谓是国内文化领域的又一大倒车。至于这番怎么样，我已经不知道怎么用言语去称赞它了。作画，音乐，配音都是超高的水准。原作的剧情底子就非常好，而制作组原创的剧情也丝毫不差。还有神一般的演出，让我不停的感叹还能这么演。这里放两个片段，折腾半天放不好，过几天把这个破主题换了，这里放一下B站港澳台的链接ep398503这是第10话中，主角团从86区离开，开始“远足”的故事，最后这一段从菲多的视角入手，做了一个非常好的原创，把故事背景再介绍了一遍（蕾娜来之前的故事），把刀子再插一遍（戴亚和安琪的恋爱，死在远足前的悠人），当你以为前面那些刀子就是全部的时候，再用菲多给你插一把大刀。ep424720这是第17话中，联邦要派遣86作为先锋队去铲除“闪蝶”，在少校和上级争辩的过程，辛又回想起了那些已经去世的86，所有的人都将自己托付给辛，但是到头来还是变成了一个个铭牌，唯一让辛托付自己的，就只有对蕾娜说的那句：“少校，我们先走了。”。ep424726这是第22话中，辛和齐利的决战，其实想放的只有那段芙蕾德丽嘉的想象，但是前面那段3D也很不错。在没有军团的世界里，齐利没有战死死，辛和雷一家没有被赶出帝国，三兄弟一起执行着诺赞家的使命，守护齐亚德王室。然而这场面还是只能存在于她的想象之中，最后齐利好像在对辛说，“照顾好她”，和雷一起离开了，我看哭了😭。可惜的是，这个番第二季真的是到了万策尽的情况，但是制作组宁愿拖更也要保证制作质量，结果就是拖到12月底还剩两集，1月又没有排挡，只能拖到3月了。剃须，然后捡到女高中生剃须，然后捡到烂裤裆，就这烂裤裆也能还能跟凉宫一起出现在这张图上，团长能文能武，一个想法可以改变宇宙，而烂裤裆会什么😅，还是说做这张图只是想讽刺新时代没有能看的 JK 了。7月小林家的龙女仆S京阿尼的归来之作，同时也是日常十周年，所以 OP 做的非常日常。问题在于新角色依露露太大了，又经常装纯+开车，导致过不了审。叔叔真行，P了半年终于给他上线了，然后🐻要么打码，要么截取画面，要么直接涂掉，真是太牛了。死神少爷与黑女仆很有看高木同学的那种感觉，但是不同的是，少爷与女仆之间的感情是很明确的，但是因为少爷死神体质的原因不能更进一步，而不是高木那种打拉扯。侦探已死2021 年了居然还有这种从上到下只有一个白毛作为卖点的破番。后期作画崩坏，肉眼都能看出来的贫穷，这也就算了，剧情也开始侮辱人的智商，总之是我今年看完的最烂的番。但是没关系，我就是来看白毛的！阴晴不定大哥哥周指活，讲述的是在电视台工作的社畜的故事，初看只是觉得好笑，我自己开始工作后再看就觉得很难绷了😭Odd Taxi哦，这个应该是4月番，但我是完结之后再补的，就放在这里吧。这个番在我心中是和 86 同地位的。围绕着出租车司机和女孩失踪事件，讲了一个完整，有趣，惊险而又刺激的悬疑推理故事，如今想要把故事讲完整就已经是一件难事了，像这样把故事讲好更是非常可贵。每一个人物都巧妙的和出租车司机和那个女孩之间联系在一起，最终让每一个人的形象都非常的饱满。还有那独特的动物外形，不仅让人物更好辨认，最后你还会发现，动物形象本身就是悬疑的一部分。LoveLive! SuperStar!性爱直播，超级巨星太好听了，简直就是天籁，你唱歌真的好好听啊，我们以后一起唱好不好，一起唱，一起做学园偶像!!!前期剧情还是很棒的，小星星那段，还有唐可可那刻入 DNA 的中文作为卖点。但是后期非要落入 LoveLive 样板戏的剧情，比如学校倒闭，死板的学生会长，显得突兀且僵硬。10月这个10月除了86啥都没看了，倒不是上班了没有时间追番。只是那些番看了两眼觉得不对胃口就没有看了，比如古见同学，比如阴阳眼见子，没有让我产生追下去的兴趣，以后有时间再看看。补番魔法少女小圆：作为一个二次元到现在才补魔圆属实不合格日常花开伊吕波，虽然番之前没看过，但是 OP 倒是耳熟能详，好看但是可惜的是不够扭曲(x
很早之前我就想整一个自己的 nas, 但是之前在学校，一没钱，二宿舍场地不合适。今年我滚出来实习了，上面两个问题也解决了，终于有机会给自己组 nas 了。机箱之所以说是买椟还珠，就是因这个机箱而起。市面上便宜又好用的，当属火了几年的蜗牛星际，正巧那天二手🐎有人￥50包邮出了一个 A 款的机箱，我就买了下来。可惜没了 A 款标志性的防尘罩，不过问题不大。主板机箱到手了，可这只是一个空壳子啊，想要让 nas 跑起来，还是得继续买买买。很多人都会推荐蜗牛原装的 J1900 主板，但我实在看不上，2021 年了，怎么还有 22nm + DDR3 的电子垃圾，至于性能就更不要说了。再往上就是 J3455, 可 J3455 跟 J1900 也没差多少嘛，而且 J3455 还有 Bug, 咸鱼上出的人也很少，价格都让我觉得不值。再往上看，就是 J4125 了，这个偏阳间一点了，2019年出的，和我笔记本算是同代了。 14nm, 4c4t, DDR4, HD 600, 支持 h265 硬解，性能上也是大幅超过 J1900，详细的对比见 J1900 vs J4125。可是 J4125 合适的主板就更难找了，咸鱼搜索 J4125，你会发现多数人都是把它当软路由用的，有很多四网口的小主机。即使加上了 『主板』 做关键词，找到的也是很多工控主板，有大量的 USB 口和串口，没有一个有很多 SATA 口适合用来做 NAS 的。尽管妖板厂华擎出了一块 J4125-ITX，但不论是海鲜市场还是淘宝，都找不到这一块主板。最后只能在矮子里面挑将军，在海鲜市场找到一块比较能用的板子，双网口，单SATA，但是有 M.2 SATA, miniPCIE 和 PCIEx1 的口可以用来转接。本来卖家是定价 ￥600，但是很多天都没卖出去，所以我小小的砍了一刀，卖家还说不包邮再送我一块 miniPCIE 转两个 SATA 的转接板，于是很快乐的收了。内存之前我笔记本上换剩下一条 8G DDR4 2666 的内存，正好用在这个板子上，这也是我为什么要挑一个支持 DDR4 的主板的原因了。电源这个就有的说了，因为这块板子并不是标准的 24pin 电源，而是 DC 电源。所以到我家之后，我翻找了半天，最后找到我之前在清水河的时候买来给路由器续命的 PD转DC 诱骗线，跟板子的 DC 口是符合，用 65w 的PD充电器成功点亮了板子。但是实际使用肯定不能这么跑啊，这个板子上只有可怜的 5v 500ma 电源，根本不可能用来给硬盘供电，所以还是要买正经一点的电源。蜗牛能使用的都是 Flex 1U 的电源，原装的那几个，口碑都不太好。同类型好一点的只有 益衡 7025B 和 海韵 SSP 300，权(mei)衡(qian) 之下，选择了 ￥245 益衡 7025B，不差钱可以加 ￥100 上海韵。然而折腾并没有结束，首先是怎么让这个电源给板子用，首先板子支持 DC 电源，同时也支持直接接12v电源线，把电源 4pin 头拆出来一根 12v 和 GND 接上就行了。还有一个问题，因为没有接20pin的接口，所以电源通电后不会启动，查询一下 20pin 接口的定义，发现绿线代表 Power_ON, 将绿线和任一个黑线短接，即可让电源通电启动。但是这样还是无法让电源和主板同时关机，意味主板关闭之后，硬盘仍然是通电的，看到网上有用继电器来解决的，但是我一想，我一个 NAS 为什么要关机呢？于是没有做了。最后还有一点要注意的，蜗牛的硬盘背板很差劲，我这块背板上甚至连电容都没有，所以尽量不要热插拔硬盘。硬盘国行 ￥1650 的 HC550 16t 上车就完事了，双12还价保退了我 ￥15，不过因为没钱只买了一块，不过本来也不准备组 RAID，就一个硬盘一个挂载点算了。然后又淘了个 99 块钱的 128g mlc msata 固态做系统盘，不过论便宜还是 QLC SATA 固态便宜，但是我不想再转接了，太扭曲了。我本来以为那个接口是 msata，结果买回来才发现接不上，它是 m.2 sata, 倒是能插上 mini PCIE 的接口，一查，miniPCIE 和 msata 接口物理上是一样的，但是走的协议不一样，只能退货重新买了个 m.2 sata 的 SSD.风扇这个是差不多装好了才发现的坑，蜗牛本来是用来挖矿的，大概没有考虑过噪音的问题，原装的风扇不支持调速，1000多转的速度，虽然放在客厅我房间里听不到了，但是离得比较近的室友就不知道了。考虑到声音还是挺大的，于是又花钱买了个支持 PWN 调速的 8cm 风扇，这下安静了。系统理论上这个配置跟群晖的 DS920+ 是相近的，用来裸机跑群晖问题不是很大，但我感觉群晖限制太多了，不够用。于是当然是掏出人见人爱的 PVE，做个虚拟化，然后装 DSM 7，怎么装我应该在锐角云那篇提过了，不赘述了。说到这个锐角云，因为一直没有硬盘，没办法做 NAS，单网口做软路由也显得很蠢，最后只能在上面跑一个 wg，做回校 VPN 的网关 （还好有 v6)，在公司的时候也会连这个 VPN 回家摸鱼。花费不计硬盘，前前后后的花费如下物品来源价格J4125 主板咸鱼580蜗牛星际机箱咸鱼508G 2666 内存条自己的0益衡 4025B淘宝245Arctic 8cm PWM 风扇京东472m ATX 电源线淘宝10.8螺丝套装淘宝20.5闪迪 x600 128g SSD淘宝89合计 1042.3算下来刚刚 1000 出头，怎么算都性价比比同配置的 DS920+ 不知道高到哪里去了。