关于 DeepSeek-R1-Distill-Qwen-1.5B 显存占用比下载权重高非常多的现象分析
现象从 huggingface 上下载下来的 DeepSeek-R1-Distill-Qwen-1.5B 模型权重大小只有 3.5GB,但是加载到 GPU 上之后,占用显存达到 5.4GB,多了 1.9GB。 排查显存占用比权重多 1.9GB 的原因应该是模型在创建的时候申请了权重以外的内存,这部分内存没有体现在权重的大小中。 使用 pytorch 查看显存占用pytorch 博客 Understanding GPU Memory 1: Visualizing All Allocations over Time 中给出了 pytorch 内部的显存统计方法,函数都定义在 torch.cuda.memory 中。具体代码可以参考官方博客,下面展示显存统计结果。 可以看到从程序开始到结束,显存一直都是 5.4GB 左右,最上方有一小部分激活占用的显存,但是由于我们输入非常短,所以占用很小。从这个结果我们可以想象到,模型总显存占用比权重多的那部分显存在模型一开始就创建了,后续的应用层是无法操作的。 使用 transformers 查看显存占用transformers 的 AutoMo...
opencl-tips
为什么从 CPU 上拷贝到 Image 里使用的是【CPU -> Buffer -> Image】而不是【CPU -> Image】?因为 Image 一般都是用 RGBA 格式,需要填充为 4 通道,以及长宽 4 对齐等需求。如果在 CPU 上做,需要对内存进行补边再拷贝到 Image 里;不如【CPU -> Buffer -> Image】 的兼容性更好。前者不用考虑 clEnqueueWriteBuffer 的参数,在 buffer_to_image 内核中注意边界条件即可。 为什么大部分 OpenCL 编程使用的都是全局内存,少用局部内存/共享内存?【不使用局部内存】可能是一种编程规范。这样做有以下优点: 可以通过在线调优 local_work_size 的方式找到最优值,从而提高性能,因为使用局部内存可能需要已知的 lws 大小;如果不需要局部内存,lws 的值可以是任意的,从而可以更灵活地调优。 不同厂商(vendor)的实现可能不同,使用全局内存可以避免不同厂商的差异。比如 mali GPU 使用的是 global memo...
将 ViT 的第一个卷积层转为 MatMul,要求步长和卷积核相等
ViT 卷积层为了将输入的图片转为 patch,然后送入到 Transformer 中,ViT 第一个卷积的步长和卷积核大小是相等的,都是 16。如下图所示, 某些框架可能不支持这么大的 Conv 步长,导致无法转换相应模型。 MatMul卷积可以使用 im2col 加 matmul 来实现,但是由于 im2col 可能效率不高,所以一般不使用这个方式。 不过我们注意到 ViT 第一个卷积层里的步长和卷积核大小是相等的,所以我们可以用 reshape + transpose 来实现 im2col。代码如下: 1234567891011121314151617181920212223242526def conv2d_to_matmul(inputs, kernel, stride, padding, bias): # 保证 stride 和 kernel 的 size 相等 assert stride == kernel.shape[2] assert stride == kernel.shape[3] assert padding == 0 out...
llama.cpp Q 量化方式总结
llama.cpp Q 量化方式总结TheBloke 里有许多不同量化方式的模型,如下: 123456789CapybaraHermes-2.5-Mistral-7B-GGUF | - capybarahemers-2.5-mistral-7b.Q2_K.gguf | - capybarahemers-2.5-mistral-7b.Q3_K_L.gguf | - capybarahemers-2.5-mistral-7b.Q3_K_M.gguf | - capybarahemers-2.5-mistral-7b.Q3_K_S.gguf | - capybarahemers-2.5-mistral-7b.Q4_0.gguf | - capybarahemers-2.5-mistral-7b.Q4_K_M.gguf | - capybarahemers-2.5-mistral-7b.Q4_K_S.gguf | ... 它们都属于 llama.cpp 中 Q 量化定义的范围。llama.cpp 里的 Q 量化有两个格式:QX_Y 和 QX_K,第一种是对称量化和非对称...
MNN 自定义算子,以 AnyNet 为例
1. 介绍本文主要介绍如何在 MNN 中添加自定义算子,以 AnyNet 为例。AnyNet 添加了一个自定义算子,虽然可以用 pytorch 表达,并且导出 ONNX 和 MNN,但是节点过多,可视化工具无法很好的展示,因此本文尝试将 AnyNet 中的自定义算子添加到 MNN 中。 自定义 MNN 算子需要先将 pytorch 计算逻辑导出成 ONNX 节点,并且由 MNN 来解释。这需要首先将 pytorch 计算包装成 torch.autograd.Function,然后通过 torch.onnx.export 导出 ONNX 节点。然后通过 MNN 的 MNN::OpConverter 来解释 ONNX 节点。 2. AnyNet AnyNet 自定义了一个 SPNet 算子,并且使用 CUDA 实现了这个算子。SPN的结构,分为两种,分别是单路连接和三路连接: 实际上就是通过一个权重矩阵,上一个点迭代地给下一个点权重,对于单路连接和三路连接,有: $$\begin{align}h_{k,t} &= (1 - p_{k,t}) \cdot x_{k,...
Cosplay 视频转图片
Cosplay 视频转图片1. 介绍有很多时候 Cosplay 视频里的 Cosplayer 只有几个相同的画面,但是他们还是发出了一个短视频用于展示。我们想要把相同的画面剔除,对每个不同的画面保留一张图片。从信息论的角度来看,大部分 Cosplay 短视频的信息熵很低,只有几张图片是有意义的。这个项目就是为了尝试提取这些有意义的图片。 调研之后发现这是一个类似视频总结(Video Summarization)的问题。视频总结是一个很大的研究领域,有很多工作。这个项目只是一个简单的尝试,不会涉及到很多复杂的技术。 2. 方法受到 2401.04962v1 的启发,这个问题主要针对的是视频中的重复帧。我们可以通过计算每一帧的特征向量,然后通过聚类算法来找到相似的帧。这个项目使用了 K-means 算法来聚类。 2401.04962v1 的架构图如下: 这个工作将视频总结(Video summarization)工作分为了三个步骤: 使用 TransNetV2 对视频进行分割,得到单个短视频(shot),使用 CLIP 对每个短视频每一帧进行特征提取,得到每一个视频的特征描述...
powershell-autocomplete
Powershell 自动补全TODO: 编写 ADB 自动补全脚本,能够识别 ADB 设备并补全,类似 zsh 的补全体验 SSH 和 SCP 自动补全远程路径 从 Powershell Gallery 上使用 Install-Module 安装脚本之后,还需要使用 Import-Module 导入模块才能使用,如果希望自动加载,可以添加到 $PROFILE 中。
opencl-alloc-error
OpenCL Image 申请失败问题描述在使用 MNN 部署 ViT 的过程中,出现了这个报错: 1Alloc Image 3 x 49152 error, code:-40 解决方案这个问题是因为这个图像的尺寸超过了本设备 OpenCL 支持的最大尺寸,Adreno 630 的最大尺寸是 16384x16384,所以这个图像的尺寸 3x49152 超过了这个限制。这个尺寸出现的地方在 ViT 生成 QKV 的时候,这个尺寸是 768x2304,使用 RGBA 四通道 Image 时,reshape 之后的尺寸就是 3x49152。解决方案: 修改 MNN 的代码,将这个尺寸调整到小于 16384x16384。但是需要修改后续的计算代码 在导出模型时,不要将三个矩阵合并成一个矩阵进行计算,而是分开计算,这样就不会出现这个问题 Buffer 不会出错,可以使用 Buffer 代替 Image 进行计算,但是 MNN 没有自动 fallback 的功能,需要手动修改代码。设置 mode=72 (即 CPU 的线程参数),在 GPU 模式下它会被解释为使用 Buffer 格式还是...
Effective STL - 06 - Programming by STL
Programming by STL条款43:尽量用算法调用代替手写循环 如果你要做的是算法已经提供了的,或者非常接近于它提供的,调用泛型算法更清晰。 如果循环里要做的事非常简单,但调用算法时却需要使用绑定和适配器或者需要独立的仿函数类,你恐怕还是写循环比较好。 最后,如果你在循环里做的事相当长或相当复杂,天平再次倾向于算法。 条款44:尽量用成员函数代替同名的算法关联容器提供了count、find、lower_bound、upper_bound和equal_range,而list提供了remove、remove_if、unique、sort、merge和reverse。大多数情况下,你应该用成员函数代替算法。这样做有两个理由。首先,成员函数更快。其次,比起算法来,它们与容器结合得更好(尤其是关联容器)。那是因为同名的算法和成员函数通常并不是是一样的。 12345678910111213set<int> s;for (int i = 0; i < 1'000'000; i++) s.insert(i);// 1ms// 使用 rand 避...
Effective STL - 05 - Functor
Functor条款38:把仿函数类设计为用于值传递C 中的 qsort 使用的 函数指针 就是指针的拷贝,因此是一个默认的值传递。 因为函数对象以值传递和返回,你的任务就是确保当那么传递(也就是拷贝)时你的函数对象行为良好。这暗示了两个东西。第一,你的函数对象应该很小。否则它们的拷贝会很昂贵。第二,你的函数对象必须单态(也就是,非多态)——它们不能用虚函数。 不是所有的仿函数都是小的、单态的。函数对象比真的函数优越的的原因之一是仿函数可以包含你需要的所有状态。有些函数对象自然会很重,保持传这样的仿函数给STL算法和传它们的函数版本一样容易是很重要的。(像之前提到的算法中 Algorithms ,Points 类就使用了 PointsAverage 来保存对应的状态,我的实现使用了 lambda 和引用来保存求和的结果) 1234567891011121314151617181920212223242526272829/*使用 Bridge 模式*/template<typename T> // 用于修改的BPFCclass BPFCImpl: public una...