随意的世界
type: channel
link: https://www.nasuiyile.cn/
description: 这是随意的个人世界
items: 平台线程，虚拟线程，协程
items: 有趣的开源项目记录
items: JEP 491虚拟线程和synchronized
items: 常见音频的损失函数
items: 分布式事务
items: X-Engine
items: Python的 GIL
items: 音频ai学习基础
items: diffwave
items: Hubert等预训练模型

平台线程，虚拟线程，协程
type: item
date: 2026-01-20T15:44:45+08:00
description: 虚拟线程和协程平台线程在Java25中对虚拟线程做了非常多的优化。那么到底虚拟线程解决了什么问题呢？哪些场景下能用到呢？和GO语言的go routine有什么区别呢？我们从小学二年级就学过，在JDK21之前Java中只有平台线程。所谓平台线程就是将线程所有的调度权交给操作系统进行处理。操作系统会有时间片轮转，多级队列等机制来决定当前让哪个线程执行，这让写程序的人觉得操作系统的线程资源几乎是无限的。问题就出在了这里，操作系统的调度是有代价的，操作系统内核直接调度，导致线程切换都需要进入内核态，这涉及了完整的上下文的切换，系统需要保存和恢复寄存器，恢复上下文，进行调度决策，这部分的开销较大。当我们创建的线程数远大于实际上操作系统逻辑核心数时，操作系统为了这些线程更公平的调度就需要频繁的挂起和恢复线程，确保每个任务都能向前推进。线代的操作系统使用的都是抢占式调度，操作系统可以通过中断的方式抢占当前的CPU并进行上下文切换’这种切换是强制的称为抢占式多任务处理模式。我们这里以一个tomcat服务器为例，如果CPU有10个核心，那么这台机器如果只能同时处理10个http请求，这显然是不能接受的。但我们发现，这个http请求其实大部分的时间，并不是在使用CPU做计算，这个HTTP请求在处理过程中可能需要调用第三方的服务接口，或者是查询数据库，redis缓存等操作。在执行这些操作的时候，我们自然希望让操作系统能够挂起这些线程切换到其他线程来执行。当然同时处理过多的线程还是会出现上面提到的CPU资源被抢光，因此tomcat设置了一个默认值，也就是200作为线程池的大小。也就是说最多同时调度处理200个请求。当然具体的参数应该按照负载的类型和CPU的型号进行修改，比如CPU如果配置好，或者当前服务只是单纯的一个请求转发服务的话我们就可以将这个参数调的更高。是不是听起来非常的熟悉，没错，这就是我们经常背的八股文IO密集型和CPU密集型线程池的资源设置逻辑。虚拟线程-协作式调度所谓IO密集型的潜在含义是，这个服务不太用CPU，经常可以把CPU出让给其他线程使用。如果线程池中线程数设置的太多了，容易导致操作系统频繁的挂起程序导致性能下降，但设置的太少了也会导致在线程数被打满的情况下，处理器依旧没有满负荷运行导致性能浪费。那有没有什么方式可以解决这个问题呢？有的兄弟有的，那就是Java的虚拟线程。虚拟线程采用的解决方式十分的简单。既然操作系统的调度那么浪费时间，我把这个调度放到用户态不就好了？JVM可以自己调度线程。在Java中对虚拟线程采用的调度方式叫做协作式调度。协作式调度是当一个程序出现挂起或者阻塞，比如sleep函数时，那么当前的CPU资源就会默认被分配给另一个需要执行的线程。在这段java代码中，程序中创建了比当系统可支配调度的线程数多一个的线程。这种情况下，多的那一个线程仍然可以被运行。比如运行如下代码时，程序能够成功的输出这里的New virtual threadimport java.util.ArrayList; import java.util.List; public class Main { public static void main(String[] args) throws InterruptedException { int busyThreads = Runtime.getRuntime().availableProcessors(); int newThreadIndex = 0; List<Thread> threads = new ArrayList<>(); // 创建并启动"死循环虚拟线程" for (int i = 0; i < busyThreads; i++) { int id = i; Thread virtualThread = Thread.ofVirtual() .name("busy-virtual-thread-" + id) .start(() -> { System.out.println("Busy virtual thread " + id + " started"); while (true) { try { Thread.sleep(1); } catch (InterruptedException e) { throw new RuntimeException(e); } } }); threads.add(virtualThread); } // 等待一会儿，让死循环线程占满平台线程 Thread.sleep(1000); // 创建并启动一个新虚拟线程尝试执行 Thread newVirtualThread = Thread.ofVirtual() .name("new-…

有趣的开源项目记录
type: item
date: 2026-01-04T11:04:02+08:00
description: 有趣的开源项目记录分类整理1. 开发与编程工具Go-blueprint - 命令行交互式创建和初始化Go项目，支持网页端选择Pake - 用Rust开发的工具，可将网页打包成macOS、Windows、Linux支持的桌面客户端应用，封装体积比Electron小20倍manim - 3Blue1Brown作者开发的强大数学动画引擎，专为数学和科学可视化设计，推荐使用社区版manimCEDeepface - 轻量级Python人脸识别工具，支持年龄、性别、情绪、种族识别，准确率超过人类RealtimeSTT - 开源实时语音转文本库，采用流式处理技术，延迟极低，支持模型量化Amphion - 开源音频、音乐和语音生成工具包，支持TTS、SVC、TTA、VC等功能Skyvern - 基于视觉理解的网页自动化工具，摆脱传统DOM依赖，采用多智能体协作架构index-tts2 - 文本转语音工具2. 数据库与数据处理Teable - 现代化的无代码数据库与数据协作平台，基于PostgreSQL构建，类Airtable界面DiceDB - 开源高性能内存数据库，作为Redis替代品，适合高速缓存和实时系统Docling - 文档解析工具，支持导出markdown和JSON，支持OCRGitdiagram - 将GitHub仓库转换为交互式架构图3. 系统工具与操作系统PowerToys - Windows系统增强工具，包含快速启动器、图片批量处理、拾色器、文本提取器等Ventoy - 多系统启动U盘制作工具Windows via Docker - 从Docker安装Windows，在Linux中通过浏览器访问，支持Win7-11WebVM - 在网页端运行的Linux虚拟机，甚至可以运行可执行文件Jellyfinweb - 媒体服务器Web界面4. 网络与文件共享LocalSend - 本地网络安全文件共享工具，无需互联网连接FilePizza - 基于WebRTC的点对点文件传输，文件直接从发送者浏览器传到接收者浏览器File-transfer-go - Go语言实现的文件传输工具Netbird - 点对点VPN/内网穿透工具AList - 聚合网盘工具Linux Mirrors - Linux镜像源管理工具5. 监控与管理Infisical - 开源密钥管理工具，类似HashiCorp Vault，支持微服务架构Beszel - 轻量级服务器监控平台，支持Docker容器监控，资源占用低XPipe - 终端连接工具Openlist - 开源列表管理工具6. 安全与渗透测试Hacking tool - 一体化黑客工具箱，包含各类漏洞扫描和攻击工具Sherlock - 社交媒体账号搜索工具，支持400多个网站查询7. 人工智能与分布式计算EXO - 分布式AI计算集群框架，允许连接日常设备形成AI计算集群Pansou - 开源搜索引擎Weepwiki - 知识库/wiki系统项目详细记录开发与编程工具Go-blueprint通过命令行交互式的创建和初始化Go的项目。也可以在网页里直接选择。Pake一个用Rust开发的小工具，可以把网页打包成macOS、Windows、Linux三种系统支持的桌面客户端应用。Pake的封装比Electron的封装小20倍。manim3Blue1Brown的作者开发的强大动画引擎。专为数学和可视化科学设计。能生成高质量的数学动画。推荐使用manim和manimCE（社区版），因为会更稳定。DeepfaceDeepface是一个轻量级的Python人脸识别工具。支持年龄、性别、情绪、种族识别。它是混合面部识别框架，集成了多种识别模型。准确率已经超过了人类。RealtimeSTT是一个开源的实时语音转文本（Speech-to-Text，STT）库，专为低延迟应用设计。该项目由KoljaB开发，采用Python语言编写，能够将语音数据实时转换为文本，适用于语音助手、实时字幕生成、会议记录等多种场景。采用流式处理技术，在语音输入的同时实时输出转写结果，延迟极低。基础模型（tiny）运行时内存占用约400MB，大型模型（large-v2）在GPU环境下显存占用约4.2GB。通过INT8量化技术，模型内存占用可减少75%。AmphionAmphion是一个由上海人工智能实验室、香港中文大学（深圳）数据科学学院和深圳市大数据研究院联合开发的开源音频、音乐和语音生成工具包。该项目于2023年12月开源，旨在支持可重复的研究，帮助初级研究人员和工程师快速进入音频生成领域。它支持TTS、SVC、TTA、VC等功能。SkyvernSkyvern的最大突破在于摆脱了传统自动化工具对DOM结构和XPath选择器的依赖。传统工具如Se…

JEP 491虚拟线程和synchronized
type: item
date: 2025-12-18T09:20:50+08:00
description: JEP 491虚拟线程和synchronizedJava24解决了虚拟线程同步使用问题 - 极道调度方式调度方式分为协作式调度和抢占式调度协作式调度：线程不让出执行权会导致其他线程饿死。抢占式调度：操作系统或运行时强制中断正在运行的线程，将 CPU 分配给其他线程。是否切断线程由调度器决定。即使你的 Java 代码没有调用 yield()，操作系统仍然可以在任何时刻抢占线程。在java中虚拟线程就是协作式调度，平台线程就是抢占式调度。Java 虚拟线程（Virtual Thread）只有在发生“可挂起（park）”的点时，才会与当前平台线程（carrier / platform thread）解绑，平台线程才有机会去执行其他虚拟线程。如阻塞，或者线程礼让。import java.util.concurrent.Executors; import java.util.concurrent.ExecutorService; public class Main { public static void main(String[] args) throws InterruptedException { // 使用虚拟线程创建一个固定数量的 carrier threads ExecutorService executor = Executors.newThreadPerTaskExecutor(Thread.ofVirtual().factory()); //平台线程小于50 int busyThreads = 50; int newThreadIndex = 0; // 提交“死循环虚拟线程” for (int i = 0; i < busyThreads; i++) { int id = i; executor.submit(() -> { System.out.println("Busy virtual thread " + id + " started"); while (true) { // 纯 CPU 循环，不阻塞 } }); } // 等待一会儿，让死循环线程占满平台线程 Thread.sleep(1000); // 提交一个新虚拟线程尝试执行 executor.submit(() -> { System.out.println("New virtual thread " + newThreadIndex + " started"); }); // 注意：你可能看不到 New virtual thread 的打印 // 因为所有平台线程都被占满 Thread.sleep(10000); } } 上述发生的原因是java的协程是协作式调度的go1.13之前是协作式调度为主，1.14之后支持抢占式调度。现在 Go 采用的是混合调度策略，兼具两种调度的优势：运行时向 M（OS线程）发送信号实现强制抢占，防止 goroutine 长时间占用 CPUpackage main import ( "fmt" "time" ) func main() { busyThreads := 50 newThreadIndex := 0 // 启动"死循环"goroutine for i := 0; i < busyThreads; i++ { id := i go func() { fmt.Printf("Busy goroutine %d started\n", id) // 纯 CPU 循环，不阻塞 for { } }() } // 等待一会儿，让死循环 goroutine 开始执行 time.Sleep(2 * time.Second) // 提交新 goroutine go func() { fmt.Printf("New goroutine %d started\n", newThreadIndex) }() // 等待 10 秒 time.Sleep(10 * time.Second) } 因此go就不会出现这样的问题。但在1.13中就会有问题。java21和25都存在这个问题。背景简单来说就是synchronized阻塞的时候，无法切换到其他协程，导致当前平台线程被阻塞，不能运行其他虚拟线程。理论上可以使用ReentrantLock来解决虚拟线程是Java 21通过JEP 444引入的轻量级线程，由JDK而非操作系统提供。虚拟线程显著减少了开发、维护和观察高吞吐量并发应用程序的工作量，使应用程序能够使用大量线程。线程必须被_调度_才能做有用的工作，即被分配到处理器核心上执行。对于平台线程，JDK依赖于操作系统的调度器。对于虚拟线程，JDK有自己的调度器，它将虚拟线程分配给平台线程，然后由操作系统像往常一样调度。虚拟线程在执行阻塞操…

常见音频的损失函数
type: item
date: 2025-12-11T19:28:11+08:00
description: 常见音频的损失函数MSE均方误差损失$$ MSE=\frac{1}{n}\sum_{i=1}^n(y_i-\hat{y}_i)^2 $$公式n：表示样本的数量比如100张图片n就是100i：第i个样本$y_i$表示模型对滴i个样本的预测值$y_i-\hat y_i$是单个样本的预测误差$\sum$为求和1/n对总和求平均，因此也叫均方误差最小二乘损失最小二乘损失的核心思想是：通过最小化预测值与真实值之间差距的平方和，使得模型预测接近真实的情况。二乘指的是平方，我们不直接计算差距（残差），而是计算差距的平方。最小：我们的优化目标，让这个平方和达到最小。最小二乘法就是最小化平方和的方法，其对应的损失函数就是最小二乘损失函数最小二乘法是一种数学优化技术，它通过最小化误差的平方和，来找到一组数据的最佳拟合曲线（或直线）为什么二乘就是平方呢？想象一下，我们在三点图上画一条直线，试图让它尽可能地穿过靠近所有的数据点。每个数据点与直线在垂直方向上的距离，就是该点的误差（也就残差）。点在直线上方，误差为正，点在直线下方，误差为负。如果直接把所有误差相加，正负误差会相互抵消，这显然不合理。因此就将误差进行平方再相加。这个所有误差的平方之和，就是我们要最小化的目标。线性化回归就是最小二乘法的使用例子之一。对于单个样本，其损失是预测值$y_i$和真实值$\hat{y}_i$的平方$$ L(y_i,\hat{y_i})=(y_i-\hat{y_i})^2 $$数据集中，所有样本的损失的平均值，就是均方误差$$ L(y_i,\hat{y_i})=(y_i-\hat{y_i})^2 $$MAEMean absolute errorL1 loss这个名称来源于数学本质，即计算的是绝对差值的 L1范数。MAE这个名称更偏向于统计学意义，即平均绝对误差。他的目的是衡量预测值和真实值之间的误差。计算绝对值的平均值$$ \mathrm{MAE}=\frac{1}{n}\sum_{i=1}^n|y_i-\hat{y}_i| $$L1指的是L1范数，也称为曼哈顿距离。L1范数是计算向量各元素绝对值之和。由于使用的是绝对值而不是平方，当预测值与真实值相差巨大时（离群点），MAE 的惩罚增长是线性的，而 MSE 的惩罚是平方增长的。因此，MAE 比 MSE 对离群点更具鲁棒性。在反向传播计算梯度时，MAE 的导数是常数（±1），这有利于稳定的训练，但可能在接近最优解时收敛变慢。它的单位与预测目标单位一致，易于理解和解释。例如，预测房价（万元），MAE 是 5，就表示平均预测偏差了 5 万元。MSE损失的收敛目标是均值，而MAE的是中位数。STFT多分辨率损失多分辨率STFT loss通过在不同分辨率下计算短时傅里叶变换（STFT）佬捕捉音频信号的多尺度特征。核心思想是使用不同的STFT参数配置，在多个尺度上衡量生成音频与真实音频在频域上的差异。STFT将时域上的音频信号转化为时频上的表示，即频谱图幅度谱：表示了每个频率成分在特定时间点上的能量强弱。这是我们人耳感知音量的主要依据。相位谱：表示了每个频率在充分太热定时间点上的起始位置。它对于音频的清晰度和时序至关重要。I音频模型通常先生成目标的频谱图（尤其是幅度谱），然后再通过一个声码器（如Griffin-Lim或神经网络声码器）将频谱图转换回时域音频。因此，在频谱图层面设计良好的损失函数至关重要。频谱收敛损失：Magnitude-based Loss幅度损失是主要用于处理频谱图或其他时频表示，核心思想是比较生成音频与目标音频在幅度谱上的差异。在需要进度重建音频波形的任务中，它是核心的组成部分。但它不会单独使用$$ L_{sc}(x,\tilde{x})=\frac{\||STFT(x)|-|STFT(\tilde{x})|\|_F}{\||STFT(x)|\|_F} $$这个损失函数有一个更全局的视角，它关注的是生成频谱和目标频谱在形状上的一致性。SC损失将频谱图视为一个高维向量，并计算这两个向量之间的“夹角”或“方向”差异。它本质上计算的是归一化后的生成频谱与目标频谱之间的差异。你可以把它想象成：我们不直接比较两个向量的长度（那是幅度损失关心的），而是比较它们的方向是否一致。如果方向一致，说明频谱的“轮廓”或“相对能量分布”是正确的。对数幅度谱损失这个损失函数直接比较生成音频和目标音频的幅度谱。在幅度谱上计算生成结果与真实结果之间的差异。由于人耳对声音强度的感知是对数性质的（例如，声音强度增加一倍，我们感知到的响度变化不是线性的），所以通常会在对数尺度下进行比较，这更符合人耳的听觉特性。对数L1损失(更常用、更稳定):$$L_{log-stft-L1}=\mathbb{E}\left[\left\|\log(|S_{ta…

分布式事务
type: item
date: 2025-12-08T21:23:00+08:00
description: 分布式事务https://www.bilibili.com/video/BV1U341157BX事务ACID在分布式场景下ACID特性含义会被扩展。原子性（Atomicity）一个事务内涉及到的分布式多个节点的操作要么一起成功，要么一起失败一致性（Consistency）：跨节点场景下也不能出现中间状态。隔离性（Isolation）：一个分布式事务在一个节点哪怕完成了，在整个分布式事务完成之前，也不能被其他事务读取使用持久性（Durability）事务提交完成后的数据不仅要在当前节点保存，还应该在分布式场景下有备份高克yognxing早期分布式事务的三个方向分别是俩阶段模型，XA事务模型TCC事务模型Saga事务模型常见的方案俩阶段提交一个直接的思路就是三个系统都等到整个交易快照完成时再一起提交。根据这个思路就诞生了俩阶段提交的方法，阶段1协调器收到客户端commit命令，决定提交T。然后协调器向各节点发送prepare T消息，各节点决定该节点上T是提交还是中止并返回给协调器。第二阶段协调器收到各个节点的ready或don't commit响应，如果都是ready，那么就决定提交T，并向所有节点发送commit消息；如果收到来自一个节点或多个节点的don't commit T的消息，就决定丢弃T，并向所有节点发送abort的消息。XA事务就是把俩阶段提交标准化后形成指定的数据库接口，再由一个事务管理器作为协调者统一管理俩阶段提交，相当于标准化的俩阶段提交。很多单机数据库，如oracle、DB2、Mysql支持XA接口，应用程序实现的一个事务管理器，然后跳读下面数据库的接口实现俩阶段提交，从而实现分布式事务。XA事务允许各个系统选择不同类型的数据库，只要它们都支持XA接口。TCC事务TCC将整个业务逻辑分为三块Try、comfirm，和cancel三个操作Try就是尝试扣除用户月、尝试扣除库存；confirm就是Try的结果都成功，可以证实把订单状态状态改为已支付，通知仓库发货；而cancel则是try失败，那么就要取消教育，回复月，回复库存。TCC是业务逻辑补偿的应用层设计实现。TCC的一个关键点是，Try阶段如果完成，那么很多状态就已经保存起来，再出现故障也不会影响事务的晚餐，也就是说，哪怕confirm阶段或cancel阶段失败，也会通过不断重试完成交易，而不是回滚。TCC的过程类似于俩阶段提交，Try过程和俩阶段prepare比较相似。但TCC没有事务管理的抽象，Try、confirm、cancel和业务场景密切绑定，要考虑具体业务来实现。SATA事务模型Saga本来是为解决长事务而出现的分布式事务模型，长事务的是需要太长时间而不允许它们保持其他事务所需要的锁的事务。电商交易如果采用俩阶段提交，则订单系统的子系统要等到用户确认收货交易完成才能提交，而物流的时间一般比较长，这个时间可能需要几天。而订单系统事务可能会拖住一些资源，如用户的余额，这个锁维持几天的时间，会导致用户无法消费，这显然是不可行的。Saga是把分布式事务看做一个个单机事务的合集，对每一个单机事务A，Saga都有一个补偿事务$A^{-1}$。如果我们执行A，又执行$A^{-1}$，那么锁产生的数据库状态同A和$A^{-1}$都未执行前一样。同样都是需要业务逻辑接入，其中TCC适用于对一致性要求高、性能敏感的场景。SAGA的核心的回滚，像一个执行-如失败反向补偿的厂业务流程，适用于执行流程长，业务流程复杂的场景。体现在分布式整体上，只要我们能确认一个单机事务都执行成功，那么整个分布式事务就是成功的，可以提交。如果任意一个单机事务不成功，那么就要把它和它之前的单机事务回滚掉，但它之前的单机事务已经提交过了，无法回滚，这时候就需要执行它们的补偿事务。电商交易系统的例子可以通过设计补偿事务，把库存和用户余额加回，来实现分布式事务。分布式事务模型传奇基地交易 ：： ShardingSphere在单机数据库的基础上是心爱一个中间层（proxy），中间层基于单机事务加saga来实现分布式事务。高可用通过单机数据库的主备复制实现。如ShardingSphere-Proxy中，可以通过配置将分布式事务管理委托给Seata。当ShardingSphere-Proxy接收到一个跨多个数据库节点的事务请求时，它会将事务信息发送给Seata的全局事务协调器。全局事务协调器根据事务类型（如Base事务）和配置规则，对事务进行协调和管理。在事务执行过程中，ShardingSphere-Proxy会负责将事务操作路由到相应的数据库节点，并监控事务的执行状态。一旦事务执行完成，Seata会根据事务的执行结果，进行全局的提交或回滚操作，确保分布式事务的原子性和一致性。原生数据库：它们基于俩…

X-Engine
type: item
date: 2025-12-08T17:29:14+08:00
description: X-Engine背景笔记来自 https://www.bilibili.com/video/BV1rS4y1N7YKX-Engine是阿里云数据库PolarDB的OLTP存储引擎（基于Mysql），主要针对电商 平台的工作负载开发，双11期间的工作负载有以下特征。活动开始时每秒交易量激增（TPS）称之为海啸问题（The tsunami problem）海啸问题：双十一零点时的TPS增加到原来的122倍，在2018年双十一的最高峰时间段，每秒钟同事进行491000笔交易，这意味着数据库同时在处理的事务达到了七千万，阿里团队希望这样的压力下依旧能维持稳定的响应时间。我们可以看到蓝色的线在红色的线夸张的涨幅下，仍旧维持了一个相对稳定的表现。缓存（Buffer）很快会被大量数据填满，我们称之为泄洪问题（the flood discharge problem）泄洪问题：随着写入量的迅速增加，存储引擎的内存缓存会被大量数据迅速填满。这时，我们就需要对新增的数据进行泄洪，也就是尽快把数据转移到持久化内存中。这也意味着我们需要一个写入能力很强的存储结构。随着促销活动的转变，数据的冷热会发生迅速的切换，称之为访问热点急速变化问题（The fast-moving current problem）数据访问局部性是缓存能够生效的关键，但是双十一中大量的整点秒杀活动，会让一些非常冷的数据变成热点数据。这时候一般数据库的存储引擎很难对这种快速的切换做出反应。从而造成缓存命中率的大幅度下降。X-Engine就是需要能够快速从持久化存储中取出新的热点数据，并且放到缓存中来解决这个问题。访问热点急速变化问题：双十一会有各种促销活动，比如整点秒杀（seckill）活动，会使当前数据的冷热急速变化。此时数据引擎需要对数据热点的变更做出反应，快速持久化存储中取出新的热点数据。LSM因为电商业务涉及到大量数据的插入和修改，所以X-Engine采用了有杰出性能的LSM-Tree。其写入的操作不做原地更新，而是以追加的方式写入内存。每一次写到一定程度，即冻结一层，写入持久化存储。一条数据->写入memtable->flush到持久化存储->经compaction操作进入持久化存储的更下层->覆盖同意key的老版本数据/同一key的新数据到来时丢弃。真正删除老数据是在后台的compaction（合并）过程中完成的。当多个SSTable文件合并时，系统和会保留最新版本的数据，删除旧版本的数据和墓碑标记，从而释放磁盘空间。compaction操作不断地把相邻的层次数据合并，并写入更低的层次。合并的过程实际上是吧相邻俩层数据读出，按key排序，想通的key之保留更新的版本。LSM-Tree的特点是写强而读弱，我们可以简单理解为LSM-Tree的写入能力比B+ Tree搞一个数量级，读能力弱一个数量级。这是因为LSM-Tree的数据可能存在多个版本。PceanBase和Tidb底层都是用的LSM-Tree。整体架构图中上面的数据方方框就是内存，下面的数据方框就是磁盘。内存中的数据都是红色的，表示热数据，橙色表示温数据，浅黄色表示冷数据。越热的数据会越往上层放。写入：写入中x-engine会和其他数据库一样做一个redo log落盘操作。然后把数据写在active memtable里面。这里是一个脏页的概念，一个active memtable写满后，他会变成一个Immutable memtable。这时候Memtable就不会做改变了，Immutable memtable 会等待一个Flash操作，进入磁盘中变成持久化存储。持久化存储越上面就是越昂贵的磁盘。比如最上面是NVMe，下层是SSD，下层是HDD。磁盘的价格是由贵到便宜，价格也是由高到底。下层之后就是依次往下合并变成一个冷数据。读取：首先我们要读取磁盘里面的active memtable和immutable memtable。X-Engine总体可以分为内存中的热数据层和持久化存储的 温、冷数据层。内存中有active memtable和多个Immutable memtable以及Read Cache和Index。硬盘中的数据分为多层，不同层的数据通过不同的存储设备存储，每一层数据以Extent为单位组织。X-Engine通过MVCC和2PL（二阶段锁）实现了SI（Snapshot Isolation 快照隔离）和RC隔离级别。SI的核心特点是：事务启动时获取一个数据库快照，该事物的所有操作都基于这个快照，不会看到其他事物的修改。提交时，只有当机遇该快照的任何并发修改与该事物的修改没有冲突时才会成功提交。SI避免了ANSI SQL-92标准定义的三种并发异常：脏读、不可重复读、幻读。读操作不会被阻塞，对于读多些少的应用场景性…

Python的 GIL
type: item
date: 2025-12-06T13:44:44+08:00
description: Python的 GIL背景一个进程可以有好几个线程。这些线程会共享进程内的所有内存。比如可以读写同样的变量。当一个进程有不止一个线程的时候，就会出现一个情况叫racing。因为这些进程中的线程有可能同时运行，也有可能交替运行。但不管是同时运行还是交替运行，都没有办法控制他们之间的相对顺序。a=1 if a>0: a-=1当俩个程序同时执行如上代码时就会出现a被减了俩次。发生了多线程竞争问题。python使用的垃圾收集算法是引用计数。如果一个进程有多个线程在运行的话就会有竞争问题。因为--操作实际上是进行了多个步骤。就可能会使得计数错误。就没有办法保证每一个python对象被正确释放了。最常用的解决方法是加锁，每次只有一个人能进入代码块运行。因此当时python的设计者们决定给python设计一个全局锁，也就是GIL。python通过这种机制保证每个程序在执行的时候都是拿到线程锁的。也就是说没有bytecode能被其他线程打断，因此在bytecode中运行的每一个c程序都是线程安全的。这种全局锁的好处是，简单，避免死锁，对于多线程的程序或者是没法并行多线程的程序这种全局锁的性能是非常优秀的。因为要锁是非常花时间的场景，全局锁就保证了每一个字节码运行的时候，最多只要一次锁，如果每个object都要访问很多的锁，那可能需要很多的拿锁时间。这也让给python写c扩展的时候方便了很多，因为可以确定在每一个bytecode运行的时候，没有线程竞争冒险的问题。这样在c代码中修改python对象的时候就不用管乱七八糟的锁。许多人尝试从python中拿走GIL，但是每次尝试都失败了，特别是先前兼容的问题。其实python比java还要大。是上个世纪90年代初的语言（早java4年）当时多核根本不存在，多线程存在的意义就是这些线程可以轮流执行，不会因为某个线程计算量过大，而卡住其他的事情。但是在21世纪后多核已经是电脑的标配。因此现在GIL出现了严重的水土不服。python的多线程在这种情况下没有办法利用多核来增加自己的计算速度。 python如今使用多进程来解决这个问题。虽然一个进程没有办法用多个cpu，但是我可以有很多个进程。from multiprocessing import Pool def f(x): return x * x if __name__ == '__main__': with Pool(5) as p: print(p.map(f, [1, 2, 3]))或者写c程序 在c语言中做多线程。当然理论上也可以尝试用一些python中没有GIL的解释器。jython就是没有GIL的。线程竞争import threading counter = 0 # 全局变量 def increment(): global counter for _ in range(100000): temp = counter # 1. 读取当前值 temp = temp + 1 # 2. 增加值 counter = temp # 3. 写回 # 创建两个线程 t1 = threading.Thread(target=increment) t2 = threading.Thread(target=increment) t1.start() t2.start() t1.join() t2.join() print(f"Final counter: {counter}") # 期望是 200000，但实际通常小于 200000在python3.11中运行代码，上述代码的值就是200w，但是如果用3.9来运行这个程序就会发现这个值不是200w，竞争冒险就出现了线程A执行完temp=counter后可能释放GIL（比如时间片到了）线程B获得GIL，执行完整的counter+=1线程 A 重新获得 GIL，继续从之前的状态执行，覆盖了线程 B 的更新GIL 只保证字节码执行的原子性，但不保证多行字节码操作的原子性！但是当前这个情况是一个行为而不是一个特性，所以任何人都不应该这样写代码。在未来的python版本中随时有可能会变化。要安全地访问共享的全局变量，你需要显式地使用线程锁：import threading counter = 0 lock = threading.Lock() # 创建一个同步锁 def increment(): global counter for _ in range(100000): with lock: # 获取锁 counter += 1 # 离开 with 块时自动释放锁 # 或者显式地： def increment_explicit(): global counter for _ in range(100000): lock.…

音频ai学习基础
type: item
date: 2025-11-30T14:50:00+08:00
description: 音频学习基础DAC传统的 的音频生成模型在压缩音频信号时存在以下问题音频伪影高频细节丢失模型通常针对特定音频类型，缺乏设计通用性https://github.com/descriptinc/descript-audio-codecDAC方案用AI对音频进行高倍率的压缩和解压缩，同时尽可能保证出色的音质。它可以将44.1kHz的音频压缩到8 kbps的极低比特率。这相当于约90倍的压缩率，意味着文件体积会变得非常小。VQ-VAE的目标是学习一个离散的潜空间。核心是编码器解码器结构，中间加入了向量量化步骤。传统VQ-VAE存在码本坍塌问题（部分码本未被使用）导致有效比特率降低论文中提出讲编码向量投影到低维空间（如8维）进行码本查找，再映射回高维。将欧氏距离转化为余弦相似度，提升训练稳定性。Diffusion TransformerDiffusion里面的骨干网络（网络架构），几乎用的都是U-net。也就是用U-Net去预测噪声。以往大多数论文都是研究如何改进diffusion本身的数学理论，很少关注它的骨干网络。那么能不能把这个架构换成Transformer呢？DiT模型基于潜空间扩散模型（latent diffusion LDM）框架，将图像编码为潜表示后，使用Transformer对其进行处理。音频中的常见操作STFTFT也就是傅里叶变换可以将信号从时域转换到频域。告诉我们这个信号是由哪些频率组成的。短时傅里叶变换。STFT通过对音频分段加窗 并对每段做Fourier Transformer，你会获得时间-频率（time-frequency）分布。现实中的信号很多都是非平稳的，它们的频率成分会随着时间的变换。比如一首曲子现有钢琴声，后由鼓声，频率分成在变化。既然整个信号不稳定，就假设信号在一小段时间内是稳定的。选择一个很短的信号，如汉宁窗，只分析窗内的这一段信号对窗内的这一小段信号进行傅里叶变换，得到这一小段时间内的频率分布。这相当于得到了信号开头时刻的“局部频谱”。将窗函数向右滑动一小段距离（这个距离称为步长或跳数）。重新对窗内信号进行傅里叶变换，得到下一个时间点的局部频谱图。最终，你会得到一系列按时间顺序排列的“局部频谱”。将这些频谱并排起来，就能同时看到频率如何随时间变化了。傅里叶变换和STFT本身是可逆的数学工具，它们不会丢失信息。（不可变通常发生在我们对变换后的数据特别是丢弃相位进行人为的修改后）Mel-SpectrogramSpectrogram ：从STFT的结果中获取幅度或功率得到可视化举证。普通频谱图 就像用一把均匀的尺子去测量声音，客观但不符合听觉。梅尔频谱图 就像用一把“人耳尺”去测量，这把尺子的刻度在低端很密，在高端很疏，测量结果更符合我们实际听到的感觉。人耳在低频（<1kHz）更敏感，在高频区的感知分辨率更低。Mel 频率尺度模拟了这种心理声学特性。对音频做 STFT → 得到 magnitude/power spectrum将频率轴通过 Mel filter-bank（Mel 滤波器组）进行加权得到 Mel 频率谱通常再做 log：log-Mel spectrogram在深度学习中梅尔频谱比普通频谱更常用与人耳特性更接近压缩维度减少模型负担更平滑，更适合 CNN、Transformer 等模型处理无论是普通频谱（通常指STFT的幅度谱）还是梅尔频谱，它们在生成过程中都主动丢弃了关键信息，这使得完美的逆向还原在数学上变得不可能。DCTDCT的核心思想是将一组信号（比如一段声音，一块图像像素）分解成一系列不同频率的余弦函数组合。它特别擅长将能量集中到少数几个系数上，这个特性让它称为视频（如JPEG，MPEG）领域的基石想象你在一个嘈杂的鸡尾酒会上，能听到各种声音的总和：人们的谈话声、杯碟碰撞声、以及背景音乐声。你的耳朵听到的：是一个复杂的、混合在一起的时域信号。这就像一张图片在空间域的表示，你看到的是千千万万个不同颜色和亮度的像素点混杂在一起。DCT所做的工作：就像一个超级敏锐的耳朵，它能把你听到的混合声音分解开来，并告诉你：“这个总的声音里，包含10%的100Hz低音（贝斯声）、5%的1000Hz中音（人声）、1%的10000Hz高音（杯碟碰撞声）……”在DCT之后，信号被表示成了不同频率成分的强度（系数）。这就是频域表示。对于自然图像，大部分能量都集中在其中低频的部分低频分量代表了图像大致的轮廓和缓变的色块。比如一个人脸的模糊轮廓这非常重要，包含了图像中的主要信息高频分量：代表了图像的细节和边缘。比如人脸这周头发丝和锐利的边界。这部分通常细节丰富，但人眼对其相对不敏感，且能量较弱。自然界的音频比如音乐，人声有个特点：其主要能量通常集中在少数几个频率上（通常是低频部分），而其他大量的频率 （系数很小或…

diffwave
type: item
date: 2025-11-28T11:34:19+08:00
description: diffwave基础是语音合成任务使用的模型。原始音频通常是通过声波表达的。在语音中有三个最基本的要素，第一个是pitch（音高或音调）：frequency，它是通过波形图的频率来反映的，频率越高，音调越高我们可以观察钢琴和提琴的震动频率，在同样的时间点中钢琴震动了5次提琴震动了更多。如果我们在时间轴上进行平移我，钢琴的音调不会发生任何的变化timbre：shape是用波形的形状来表示的。这三个不同的语音波形的形状是不一样的。钢琴的波形随着时间的推移，虽然范围会发生变化，但是形状是几乎不变的。响度，loundness：range。这是通过波形的范围来表示的，范围越大响度越大，随着时间的推移，钢琴的范围是在不断的减小的，因此其响度在不断的下降。图像往往是通过三个维度来表示，对于大部分图像任务，图像的维度都是固定的，网络结构只需要做一种维度通道x宽x高而语音通常只有一个维度就是长度。声波的维度是变化的，这是因为声波每秒采用的频率是固定的，但是采样的长度是不固定的。有的时候想生成5秒的语音，有的时候想生成10秒的语音。对于一个256x256的图像下采样到128x128，其实是可以保留绝大部分信息的。只不过是分辨率稍微低了一点，模糊了一点。但是语音降采样会严重丢失信息。会从非常清晰的语音变成非常老式的电话。图像对于高频的噪音和小的缺陷并不致命，但是对于语音来说非常致命。比如数秒钟的语音出现了噪音，人耳听起来非常敏感，因此语音做这些任务会困难一些。一个22.05k时间步每秒的音频，持续九秒就等于（RGB）3x256x256 的图像的维度生成的维度是不固定的，人很难通过肉眼去理解波形图。语音需要非常高的质量。已经有的模型我们可以将任务分成几类。分类的语句是条件信息富足的程度。从富足到缺少条件信息分别可以分为Neural vocoding：声码器梅尔频谱本身并不能直接还原原始音频，因为会经过一些不可逆操作。因此单靠算法无法精确反演原始波形。声码器本质上是在做，将声学特征（mel）->近似真实的波形（audio waveform）这个映射不是传统的DSP的反向变换，而是学习出来的生成模型。波形中每个点的精确值（振幅）由频率和相位共同决定。梅尔频谱主要保留了频率和能量信息，但相位信息大量丢失。声码器的核心挑战之一就是如何高质量地估算出与梅尔频谱对应的、听起来自然的相位。拙劣的相位重建会导致语音听起来有杂音、金属感或嗡嗡声。这是条件信息非常强的任务，使用的是梅尔谱。梅尔谱是通过原始音频的STFFT得到的。包含了原始音频的绝大部分信息，声码器就是将这样的谱转化为音频，他是TTS系统里重要的组成部分。（另外一半就是通过文本生成这样的梅尔谱）在声码器领域有很多的工作。我们可以粗浅的分类为几个模型自回归模型比如wavenet就是一个维度一个维度的生成，这类模型质量非常好，但是生成的速度非常慢。是慢于实时的，生成1秒钟的音频需要5s的时长。实际应用非常困难。第二类模型是基于flow标准化流的模型。是通过likelihood训练一个可逆的神经网络。这类模型运行速度非常快，远远超过实时。但是缺点就是因为要让网络结构可逆需要的参数量会多很多，才能保证网络结构的容量VAE和GAN这俩个在图像领域流行的工作，稍微比上俩个模型要差一点点。Class-conditional和unconditional无条件生成：模型“自由发挥”，学习训练数据（如各种音乐、语音、环境声音）的整体分布，然后生成一个“典型”的、符合该分布但全新的音频样本。你无法控制它会生成什么具体内容。有条件生成：模型“按需创作”，生成过程受到一个或多个条件的约束。这个条件就像是一个指令或蓝图，告诉模型你希望生成什么样的音频。你可以控制生成结果的关键属性。条件上一个离散的类别标签然后进行生成，是条件非常弱的生成模型。unconditional：完全无监督的生成wave net也可以实现这个工作。wavenet可以生成听起来像单词的语音，但是质量会差一些。我们能不能设计一个普适的模型能够在刚才提到的不同的任务里都能达到非常好的效果。为了这个目标，提出了一个模型叫做diffusion probabilistic model这类模型不是自回归的，所以他的生成速度比wavenet要快很多。它的网络结构非常灵活，不像基于流的模型，需要维护一个可逆的网络。因此这类模型能实现更小的模型体积。这个模型只涉及训练单个网络。而GAN和VAE都需要训练俩个网络。训练俩个网络的时候，需要平衡俩个网络的训练进展。在降噪扩散概率模型方面 目前没有这类的问题。首先由x0这个数据分布。然后往干净的数据里加噪音（从右向左）,经过T步之后。Diffusion process q：add Gausian noise这个扩散模型是马尔科夫链，是固…

Hubert等预训练模型
type: item
date: 2025-11-27T21:36:00+08:00
description: Hubert笔记基础不同于语言已经是离散化的符号表示，语言是连续的波形大预言模型是自回归模式的，需要一个字一个字的输入，所以想让大预言模型认识语音，就需要将连续的语音表示为离散化的单元。那么语音中的单元是什么呢？怎么样的单元才是最好的呢？最重要的是学习合适的表示形式。一句话中有许多声音的单元语音中没有统一的词典语音单元没有显示的分割标志这篇论文给出的解决方案是：自监督的方式，让模型自己学习到语音的离散表示如果用文本类比的话，我们希望输入的文本就是输出的文本希望这个模型通过上下文预测出中间三个A. MFCC（梅尔频率倒谱系数）是什么？ MFCC 是一种能够很好地反映人耳听觉特性的音频特征。人耳对低频声音的变化更敏感，对高频声音的变化不那么敏感。MFCC 通过梅尔滤波器组来模拟这一特性。为什么用它？ 相比于原始的波形数据，MFCC 更能捕捉声音的“音色”或“质感”信息。例如，不同的人说同一个元音“a”，其音高（基频）可能不同，但他们的 MFCC 特征在整体形状上会非常相似。如何得到？ 对一小段音频（例如 20-40 毫秒的一“帧”）进行计算，会得到一个 MFCC 特征向量（通常包含 12-13 个系数）。一段完整的音频（如几秒钟）会被切分成成百上千个这样的帧，因此会得到一个 MFCC 特征向量的序列。总结：一段音频 → 一个 MFCC 向量序列（一个二维矩阵，行是帧，列是MFCC系数）。梅尔频谱是一个二维图像（时间 vs. 梅尔频率），它保留了非常丰富的信息，包括我们希望用于识别的音色信息，也包含了一些我们想过滤掉的细节（如个体发音的细微差异）。MFCC 是对梅尔频谱的进一步压缩和提炼。它通过离散余弦变换（DCT） 这个工具，从每一帧的梅尔频谱中提取出最能代表“声道形状”的倒谱系数。先通过CNN Encoder，先将连续的语音离散化（得到离散的向量）通过BERT结构，也就是双向transformer结构和Mask，学习更好的离散表示（如图遮蔽其中的x2，x3，x4） 计算输出的z2，z3，z4和原来的差距算loss网络学习的目标是什么？通过k-mean聚类的MFCC，还有第一轮迭代后的中间表示算法细节CNN-Encoder实现7个1d的卷积层，步长 [5,2,2,2,2,2,2] kernel-size[10,3,3,3,3,2,2]通道数为512最终的结果是1秒语音有16000个采样点，通过这个encoder之后，转换成50个特征，也就是50个向量，每个帧代表20ms的语音内容。这里经过了5个卷积层，以第一个卷积层为例，批大小为1，通道数为512，kernel-size为10，步长为5。经过第一个卷积层后由原来的[1,1,16000]变成[1,512,3200]如何mask？随机8%的比例选择开始位置，然后把连续的10个输入mask。网络loss如何计算？mask位置的loss与非mask位置的loss按一定比例哦混合，即在上图中x1和z1算的就是非mask部分，x2和z2就是mask部分。计算输出向量与目标向量的余弦相似度39维MFCC特征是在计算效率、信息完整性和模型性能之间取得的一个经典平衡点。它并非一个绝对的标准，而是一个在语音识别研究中被广泛验证且行之有效的经验值。MFCC最核心的部分是12维的MFCC系数。这12洗漱捕捉了语音信号的短时功率的网络形状，包络包含了绝大部分与发音内容相关的信息。原始的12维MFCC是静态特征，只描述了1帧内的频谱特性。但语音是动态的，发音时舌头、嘴唇等器官的运动会导致频谱随时间变化。这种变化率（速度）至关重要。一阶差分就是来描述这种变化的，它计算的是相邻帧之间的MFCC系数的差值。为了捕捉更精准的信息，比如加速度（变化本身的变化）我们引入二阶差分。二阶差分计算的是一阶差分岁时间的变化。除了频谱形状，帧与帧之间的能量（响度）变化也是一个非常重要的线索。因此我们把之前忽略的对数能量，以及它的一阶差分和二阶差分也加入向量特征 12+12+12+3=39维这39维特征已经能够捕捉到语音信号中与识别内容相关的大部分关键信息。更多的维度（如60维）带来的性能提升很小，但计算成本和存储开销会显著增加。目标标签如何获得？wav2vec 2.0wav2vec是这个方向重要的里程碑，启发了后续很多的模型包括hubertwavlmwhisper核心问题与LLM一样，能不能对语音进行预训练，得到一个通用的底层表示模型 ，然后只需要在不同下游任务上进行微调就能达到好的性能了。输入的波形是语音x，然后将cnn输入到cnn，得到z。将z进行量化得到Q，将Z输入到Transformer-encoder进行编码获得C，C与Q对比做lossMask是如何做的随机mask，以p=6.5%的概率选择是否mask，M=10表示如…