于昼夜

西郊有密林,
助君出重围✨

PyTorch Tensor 与 TensorImpl 机制解读

PyTorch Tensor 与 TensorImpl 机制解读 1. 为什么要把 Tensor 和 TensorImpl 分成两层 初次接触 PyTorch 源码的人常有一个疑惑:Python 里 torch.Tensor 明明是一个对象,为什么 C++ 里又有 at::Tensor 和 c10::TensorImpl 两个类? 答案是值语义 vs 数据本体的分离: at::Tensor:一个轻量的"引用/handle",本身几乎不存储数据,可以随意拷贝、传参、放进容器,开销只是一次引用计数的加减。 c10::TensorImpl:真正持有 sizes/strides/dtype/device/storage 等元信息的对象,在堆上只有一份。 多个 at::Tensor 可以指向同一个 TensorImpl(比如 view 操作、赋值拷贝),它们共享同一份元信息和底层数据,改一个,另一个也能看到变化——这正是 PyTorch “view 语义"和"就地修改语义"的根基。 at::Tensor a; at::Tensor b = a; // 只拷贝了指针,refcount+1 │ │ └──────────┬─────────────┘ ▼ c10::TensorImpl (堆上唯一一份) ├── sizes/strides ├── storage_ ──────────► StorageImpl(真正的数据 buffer) ├── dtype / device └── ... 这种设计在 C++ 里通过 c10::intrusive_ptr<TensorImpl> 实现:at::Tensor 内部只有一个 intrusive_ptr<TensorImpl> 成员,拷贝 Tensor 就是拷贝这个智能指针。 ...

July 26, 2026 · 4 min · 665 words · YuZhouye · 

[VibeCoding]NTFS4Mac - 免费macOS的NTFS读写工具

背景 之前在用的NTFS读写工具失效了,想着用AI搓一个能用的和自己去找一个好用的可能耗费的精力差不多 于是花了2小时基于claude Code和 GLM 5 搓了一个NTFS可读写的mac程序 项目地址:NTFS4Mac 技术方案 核心原理 NTFS4Mac 的核心原理很简单:利用开源的 ntfs-3g 驱动通过 fuse-t 框架将NTFS分区以读写模式重新挂载。 fuse-t: 一个现代化的开源FUSE框架,不需要内核扩展,完美支持Apple Silicon ntfs-3g: 成熟稳定的开源NTFS驱动 相比传统的 macFUSE 方案,fuse-t 不需要加载内核扩展,在安全性和稳定性上更有优势。 项目架构 项目提供两种使用方式: GUI 应用 - 使用 SwiftUI 开发的原生 macOS 应用 CLI 工具 - 纯 Bash 脚本实现的命令行工具 NTFS4Mac/ ├── NTFS4Mac/ # SwiftUI GUI应用 │ ├── Models/ # 数据模型 │ ├── Services/ # 核心服务(挂载、设备检测) │ └── Views/ # SwiftUI视图 ├── lib/ # CLI脚本库 │ ├── mount.sh # 挂载逻辑 │ ├── devices.sh # 设备列表 │ └── detect.sh # 自动检测 └── ntfs-cli.sh # CLI入口 功能特性 GUI 界面 ...

April 3, 2026 · 2 min · 221 words · YuZhouye · 

Dtensor新特性FastPath

DTensor FastPath 技术分析 1. 概述 DTensor 的核心工作流是:拆包参数 → 推断输出分片策略 → 可能做 redistribute → 执行本地算子 → 包装结果。 在 PyTorch 2.9 及之前版本中,这条路径完全在 Python 中执行,入口是 __torch_dispatch__。dispatch 是存在开销的,往往被 __torch_dispatch__ 拦截后在 Python 侧的调库开销更大,甚至可能超过一些小算子的执行时间。 关键 PR:https://github.com/pytorch/pytorch/pull/167051 核心思想:将操作搬到 C++,在 python_arg_parser.cpp 的 dispatch_on_subclass 层面拦截 DTensor,绕过 __torch_dispatch__ 协议,直接进入 C++ 实现的 dispatchDTensorOp。 2. 核心机制 2.1 __torch_dispatch__ 变更至 C++ 2.1.1 旧版本 __torch_dispatch__ 拦截逻辑 在旧版中,DTensor 是一个标准的 Tensor subclass,算子分发走的是 PyTorch 的标准 __torch_dispatch__ 协议。先梳理一下 2.9 之前是怎么把 DTensor 全部通过 __torch_dispatch__ 处理的。 当 DTensor 被构造时(通过 _make_dtensor),C++ 会检测它定义了 __torch_dispatch__,于是在 tensor 的 DispatchKeySet 上设置 Python + PythonTLSSnapshot 两个 key: ...

March 26, 2026 · 5 min · 961 words · YuZhouye · 

显存碎片

CUDA 显存碎片问题:诊断与解决方案 1. 什么是显存碎片 深度学习训练过程中,PyTorch 通过 CUDA Caching Allocator 管理 GPU 显存。该 allocator 会向 CUDA driver 申请大块 segment,然后在内部按需切分为更小的 block 分配给各个 tensor。 显存碎片指的是:一个大 block 被中间插入的小 block 切分后,即使大 block 对应的 tensor 已释放,由于小 block 仍然存活,整个 segment 无法合并回收。这种碎片并非物理设备上真实的地址碎片,而是 Caching Allocator 层面的逻辑碎片。 典型场景: Step 1: allocator 申请一个大 segment [LLLLLLLLLLLLLLLLLL] Step 2: 大 tensor 释放,但小 tensor 卡在中间 [____ss____________] ↑ 小 block 存活,segment 无法合并 Step 3: 下一轮需要同样大小的连续空间 → 找不到 → OOM 当这种情况以固定周期重复发生时(例如每个训练 step),碎片会不断累积,最终导致 OOM——即使 nvidia-smi 显示仍有大量空闲显存。 2. 如何判断 OOM 是否由碎片引起 2.1 使用 PyTorch Memory Visualizer PyTorch 提供了官方的显存分析工具:https://docs.pytorch.org/memory_viz ...

March 26, 2026 · 2 min · 391 words · YuZhouye · 

PyTorch内存管理:Caching Allocator(二)一文读懂Block

前言 PyTorch内存管理:Caching Allocator 的第一部分是对PyTorch内存管理的基本情况的概括。这篇文章讲从代码入手,尝试理解PyTorch Caching Allocator 中最核心的数据结构:Block,以及它在实际内存分配中是如何被管理的。 Block 先通过代码看看它的定义: struct Block { c10::DeviceIndex device; // gpu cudaStream_t stream; // allocation stream stream_set stream_uses; // streams on which the block was used size_t size; // block size in bytes size_t requested_size; // memory originally requested BlockPool* pool{nullptr}; // owning memory pool void* ptr{nullptr}; // memory address bool allocated{false}; // in-use flag bool mapped{true}; // is the virtual address range this Block references // backed by physical pages. Always true when // expandable_segment_ is null. When false // This Block will be aligned to the segment size // of its expandable_segment_. Block* prev{nullptr}; // prev block if split from a larger allocation Block* next{nullptr}; // next block if split from a larger allocation int event_count{0}; // number of outstanding CUDA events int64_t gc_count_base{0}; // get_free_blocks_call_count when Block is inserted std::shared_ptr<GatheredContext> context_when_allocated; // only set for the first block in the segment (when prev == null) // this records the frame information when cudaMalloc was called // whereas context_when_allocated records the last time we handed this // memory out from our cache. std::shared_ptr<GatheredContext> context_when_segment_allocated; ExpandableSegment* expandable_segment_{nullptr}; device、size、ptr:这几个参数比较好理解,由于是内存管理,就一定需要涉及到分配的设备、分配的内存大小以及在设备上的地址; requested_size:分配时请求的实际大小; stream:代表分配时的stream,默认所有操作会在该流进行; stream_uses:使用多流时记录使用过的流; prev、next:说明Block本质是一个链表,这也是为什么可以很方便的在Block内部执行切分、重组操作; event_count:未完成的cuda_event计数; 还剩一些涉及到虚拟内存、可扩展段以及资源管理的描述,我们放到后文去深究。总之,这几个属性就已经决定了如何申请、释放一个block,以及存在异步冲突怎么解决。 ...

February 8, 2026 · 3 min · 579 words · YuZhouye ·