sumire

アイラちゃん

@sumire

  • 从来没觉得学习C++快乐过www——分享一个splice函数的坑

    不用_LIBCPP_DEBUG或者_GLIBCXX_DEBUG时,Asan会查不出误用stl的内存问题。并且 cmake --DCMAKE_BUILD_TYPE=debug 不会定义std debug的宏。你需要自己add_compile_options进去。

    我们分享三个例子。

    仅开启Asan-不能查到错误
    仅定义_LIBCPP_DEBUG-出现segmentation fault
    开启Asan+定义_LIBCPP_DEBUG-Asan报错并且出现segmentation fault

    一开始在实现的时候,我发现 list.splice(pos, other, it) 中的other好像是哪一个list都无所谓。后来我们(我和gemini)发现,这个other是传入对应的list,在splice转移之后减少这个other的size大小而用的。误用会导致size大小错误。

    不学C++了,学rust去了 (生气) 🐖

    Post #311 ❤️ 3 likes
  • 在这里提前祝塬友们新春快乐!

    不过アイラちゃん一直都不喜欢这种太过于热闹的节日,社交特别耗费精力而且热闹过头的节日反而让アイラちゃん感觉不适应,还是更喜欢独自一人 + 赛博网友的双休日一点。好不容易刚刚才从社交地狱中跑出来,被父母硬扯去打麻将(但实际上一点都不会)然后输个精光,在一桌的人嘴上都说的“一家人”但实际上不动声色地把你的钱一点点“骗”走的感觉真是不舒服呢~(笑)

    不管怎么样,就让最后的两个小时带去身上不快乐的记忆,迎接农历新的一年吧!

    X: @ArameDraw
    image|353x500

    Post #308 ❤️ 10 likes
  • 水文:如何在 Mac 上使用 views::enumerate?

    回到家中几乎什么都不想干,荒废了 🥺

    起因是一道简单题

    3379. 转换数组

    在尝试学习 ranges 和 views 的 アイラちゃん 显然不会放弃“简单题复杂做”的雷霆想法,因此就尝试写出了面条代码:

    class Solution {
    public:
        vector<int> constructTransformedArray(vector<int> &nums) {
            int n = nums.size();
            return nums | views::enumerate | views::transform([&](auto &&tup) { const auto &[i, num] = tup;
                                       return nums[((i + num) % n + n) % n]; }) | std::ranges::to<std::vector>();
        }
    };
    

    可以运行 [1] 来查看这个执行的结果。

    pipeline 运算符 |

    我们首先来看 pipeline 运算符的定义:在 C++20 的 ranges 库中有这样的定义:

    template<typename _Self, typename _Range>
        requires __is_range_adaptor_closure<_Self>
          && __adaptor_invocable<_Self, _Range>
        constexpr auto
        operator|(_Range&& __r, _Self&& __self)
        { return std::forward<_Self>(__self)(std::forward<_Range>(__r)); }
    
    • requires 子句要求_Self 类是一个 range_adaptor_closure 类型,这个类型是一个辅助模板类型,用于定义 RangeAdaptorClosureObject
      • RangeAdaptorClosureObject 其实就构成了 pipeline 运算符 | 的语义,如果我们有一个对象 C 是一个 RangeAdaptorClosureObject,一个 R 是一个 Range,那么我们就有以下的等价语义:[2]
      • C(R) <=> R | C
      • 更复杂一点,我们可以将多个闭包串起来:d(c(R)) <=> R | c | d
    • operator | 将会利用经典的完美转发 std::forward,因此我们实际上将会调用的是 | 右侧的 operator () 的重载。
    • views::enumerate 将会把 range 转换成一个 tuple (index, elements),在迭代过程中我们可以获取当前迭代器的下标。

    这样我们的例子实际上等效于 [3]

        int n = nums.size();
        std::vector<int> ans = std::ranges::to<std::vector>(
            std::views::transform(
                [&](auto &&tup) {
                    const auto &[i, num] = tup;
                    return nums[((i + num) % n + n) % n];
                })(
                std::views::enumerate(nums)));
    

    实际上使用的时候不要想的这么复杂。想象 | 就是一根水管。在调用 std::next(),也就是迭代的时候,| 左侧将会有一个迭代器对应的元素,经过 | 右侧的变换,得到新的元素。就像 next 是水阀,每调用一次就会将一个元素送入水管,经过加工后输出新的成品。

    在 rust 里这样的写法更加常见。我们可以:

    fn construct_transformed_array(nums: Vec<i32>) -> Vec<i32> {
            let n = nums.len();
            let ans:Vec<i32> = nums
                .iter()
                .enumerate()
                .map(|(i, &num)| nums[(((i as i32 + num as i32) % (n as i32) + (n as i32)) % (n as i32)) as usize])
                .collect();
            ans
        }
    

    编译不通过!CMake 尝试混搭 clang+libstdc++

    但是问题来了,我们在 Mac 上编译不通过!!!经过查询以后,我们很遗憾地发现 [4]

    截屏 2026-02-06 21.12.54|690x199

    这又是折腾的一个好机会,我们既不想放弃 LLVM 提供的编译器和 LLDB,也想使用 views::enumerate,但是 LLVM 对应的 libc++ 又没有实现,该怎么办呢?我们就需要在 CMakeLists.txt 里面进行魔改,来在前端和中间端采用 LLVM,后端链接上 libstdc++。

    我们还是用 brew 来折腾我们的 mac:

    brew install llvm
    brew install gcc
    

    这样我们就有了一个 clang 21 和一个 gcc 15.2。

    然后我们开始编写 CMakeLists 来魔改我们的后端。首先还是经典起手:

    cmake_minimum_required(VERSION 3.26)
    project(main)
    
    set(CMAKE_CXX_STANDARD 23)
    set(CMAKE_CXX_STANDARD_REQUIRED ON)
    set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
    

    接着我们寻找 gcc 的相关路径和头文件,设置我们的 clang 编译器,查找对应的 GCC 及其版本。

    # 可以利用 which clang which gcc 来找。
    # 一般在 linux brew 上为:/home/linuxbrew/.linuxbrew/opt/xxx
    set(LLVM_PREFIX "/opt/homebrew/opt/llvm")
    file(GLOB GCC_PREFIX "/opt/homebrew/opt/gcc") 
    set(CMAKE_C_COMPILER "${LLVM_PREFIX}/bin/clang")
    set(CMAKE_CXX_COMPILER "${LLVM_PREFIX}/bin/clang++")
    
    # 查找 GCC 版本 (我们这里是 15)
    file(GLOB GCC_INCLUDE_DIRS "${GCC_PREFIX}/include/c++/*")
    list(GET GCC_INCLUDE_DIRS 0 GCC_CPP_PATH)
    get_filename_component(GCC_VER "${GCC_CPP_PATH}" NAME) 
    message(STATUS "Found GCC Path: ${GCC_PREFIX}")
    message(STATUS "Using libstdc++ version: ${GCC_VER}")
    

    禁用相关的 clang 的默认 libc++ 的库,并且 LLVM 生成的调试信息为 gdwarf-5,我们的 Mac 自带的链接器 ld 仅支持 gdwarf-4 的调试信息。因此我们也采用 brew install lld,加上:

    add_compile_options("-nostdinc++")
    add_link_options("-fuse-ld=lld")
    

    我们需要依照顺序注入 GCC 的头文件,提供给前端的 Clang:

    include_directories(SYSTEM "${GCC_CPP_PATH}")
    # 这里根据 ls /opt/homebrew/opt/gcc/include/c++/15 中的第一个文件夹就是。这里是重要的根据对应的系统架构的相关配置,供 C++ 的内置函数进行系统调用。
    include_directories(SYSTEM "${GCC_CPP_PATH}/aarch64-apple-darwin24")
    # 兼容性文件,是一些古老的玩意儿~
    include_directories(SYSTEM "${GCC_CPP_PATH}/backward")
    

    最后补充全 clang 的相关配置,防止 stddef.h 之类的文件是丢失的导致编译失败,链接时提示链接器去 GCC 的 lib 中寻找 libstdc++。

    execute_process(COMMAND ${CMAKE_CXX_COMPILER} -print-resource-dir
        OUTPUT_VARIABLE CLANG_RESOURCE_DIR
        OUTPUT_STRIP_TRAILING_WHITESPACE
    )
    include_directories(SYSTEM "${CLANG_RESOURCE_DIR}/include")
    add_link_options("-L${GCC_PREFIX}/lib/gcc/${GCC_VER}")
    add_link_options("-stdlib=libstdc++") 
    # 显示设置 rpath。
    add_link_options("-Wl,-rpath,${GCC_PREFIX}/lib/gcc/${GCC_VER}")
    

    最后加上构建目标就可以了。

    include_directories(templates)      # 其他文件夹
    add_executable(main main.cpp)    # 目标文件
    

    这样我们就完成了利用 clang + libstdc++ 的魔幻搭配。

    什么时候使用 range?

    接下来其实有一个问题:我们什么时候使用 ranges?什么时候使用 for loop?ranges 的这类 pipe 运算符,以及 Rust 中的 map 都看起来和 for loop 的表现很像,但其实也有很大的区别。

    在 C++ 中,ranges 通常比 for loop 慢 [5],而 Rust 的性能相近 [6]。原因我还不太清楚。

    而在 Rust 中,我们其实有了一点点提示,关于什么时候使用 ranges[7]

    map() is conceptually similar to a for loop. However, as map() is
    lazy, it is best used when you’re already working with other iterators.
    If you’re doing some sort of looping for a side effect, it’s considered
    more idiomatic to use for than map().

    在执行迭代(消费)操作的时候采用 iters,而在拥有外部修改效果(打印,完全遍历 IO)的时候,我们则需要进行 for loop。


    1. Example 1 ↩︎

    2. C++ named requirements: RangeAdaptorClosureObject ↩︎

    3. Example 2 ↩︎

    4. Cppstats ↩︎

    5. std::ranges may not deliver the performance that you expect ↩︎

    6. Performance in Loops vs. Iterators ↩︎

    7. map ↩︎

    Post #305 ❤️ 3 likes
  • 准备回家了,基本上事情已经忙的差不多了,接下来一个月学习的将会非常杂,所以アイラちゃん的日记楼里将会有各种方向的奇怪知识

    今天记录的是 Rust 里面比较复杂的 所有权 (ownership), 范围 (scope), 引用 (Reference) 和 借用 (borrowing),我们通称为“移动语义 (moving semantics)”, 和 C++ 还是有些不太相同的。

    Ownership & Scope

    主要有三条规则:

    • 每个值将必须有且仅有一个 owner。
    • 超出一定范围的时候,owner 将放弃这个值。
    • 大括号可以指定变量的范围。如果没有,则变量的范围持续到最后一次使用这个变量的地方。

    对于规则一,我们有:

    let s1 = String::from("Hello");
    let s2 = s1;
    // println!({s1}) 非法,因为每个值只能在同一个 scope 内有一个 owner;
    

    对于规则二,我们有 隐形 drop 机制。

    {
        let s: String = String::from("Hello")
        // 这里我们将有一个隐形的 drop
    }
    // println!("{s}") 非法,s 已经被 drop
    

    为什么这么设计?

    因为在 Rust 中,每个 String 的值是分配在堆 (malloc) 上的,我们拿到的只是一个类似于结构体变量的东西,其中有 (ptr 指针指向那一段堆上内存,len 代表长度,capacity 代表容量) 的成员,在超出 scope 将调用隐形的 drop 释放内存的规则下,如果一个值(一段内存)被两个变量的 drop 同时释放,就会出现 double free 的情况,造成出错。

    如果我们想要 deepcopy,我们需要用 .clone() 来进行。

    函数和所有权

    caller(调用者) 会把非栈上变量的值的所有权移交给 callee(被调用者)。也就是如下所示:

    fn foo(s: String) {
        // do something;
    }
    fn main() {
        let s1 = String::from("Hello");
        foo(s1);
        // println!("{s1}");  非法,因为 s1 已经无效了。
    }
    

    同样地,callee(被调用者) 也可以通过返回非栈上变量的方式让值传递到 scope 之外。

    fn foo() -> String {
        let s = String::from("Hello");
        s          // 没有;代表这是一个 expression , 将会有返回值。
                    // 有; 代表这是一个 statement,执行一个动作。
    } // 离开 s 的变量范围,s 失效
    fn main() {
        let s1 = foo();   // s1 = "hello"
    }
    

    这样我们发现:
    caller 将变量的值传递给 callee,变量失效
    -> callee 执行操作
    -> 将变量传递回来给 caller

    如果我们不想让变量在 caller 中失效怎么办?我们就需要采用 Reference,borrowing,mutable reference 了。

    (mutable) Reference, borrowing

    如果我们不想让变量在 caller 中失效,我们可以传递一个 Reference。如下:

    fn main() {
        let s1 = String::from("hello");    // "hello" 的 owner: s1
        let len = calculate_length(&s1);  // 传递 s1 的引用,同时保持 s1 有效
        println!("The length of '{s1}' is {len}.");
    }
    fn calculate_length(s: &String) -> usize {
        s.len()    // expression 返回 s 的长度
    }
    

    但是我们注意到,此时 callee 中的 s 是不可变的。如果我们需要修改,我们就需要传递可变引用 (mutable references)。

    fn change_and_return_length(s: &mut String) -> usize {
        s.push_str(" world!");
        s.len()
    }
    fn main() {
        let mut s1 = String::from("Hello");
        let len = change_and_return_length(&mut s1);
        println!("s1: {s1} has len {len}");
    }
    

    还记得上文的规则三吗?规则三说:

    大括号可以指定变量的范围。如果没有,则变量的范围持续到最后一次使用这个变量的地方。

    对于可变的引用,我们要求针对同一个变量,只能有一个可变引用。但是多个引用是可以的。这样,我们可以在编译期防止数据竞争

    什么时候出现 Data Race?

    • 一个值在同一时间被多个指针指向;
    • 至少有一个指针在修改这个值;
    • 没有同步机制来保证对这个值的访问;

    我们来看这个例子:

    fn main() {
            let mut x = Vec::new();  // x start;
            let y = &mut x;    // y 可变引用 x; y start;
            y.push(42);          // 最后一次使用 y,y end,超出 y 的 scope
            let z = &mut x;   // z 可变引用 x; z start;
            z.push(13);          // 最后一次使用 z, z end, 超出 z 的 scope
            assert_eq!(x, [42, 13]);
    }
    

    诶?为什么这里有使用了多次对 x 的引用呢?(回忆我们的规则 3)

    悬垂引用

    最后我们来看一下这个例子:

    fn danger() -> String& {
        let s = String::from("Hello");
        
        &s
    }
    fn safe() -> String {
        let s = String::from("Hello");
        s
    }
    

    我们返回一个对 s 的引用,但是 s 在超出 scope 后就会消亡,这就会造成悬垂引用。正确的做法是上面的 safe。因为这时候 s 的值将会转移所有权到范围外,s 消亡。

    参考:Understanding the ownership

    Post #304 ❤️ 7 likes
  • 没事,直接改了更好一点,就不用等了的说 😋

    Post #301 ❤️ 1 like
  • 截屏 2026-01-20 21.13.04|690x402

    这个样子 🥺

    这是上次修改

    截屏 2026-01-20 21.13.40|690x55

    Post #299 ❤️ 1 like
  • 寒假 (或者包括下学期?) 的学习计划~

    leetcode 冬训
    分析 deepep 库
    继续学习 C++,看 C++ templates
    看 Seven Concurrency Model in Seven Weeks
    看新版的 cuda programming guide
    准备中期答辩

    Post #298 ❤️ 2 likes
  • ascah:

    万一别人先比我发了怎么办?

    我也问过我的博士学长,如果有人和我们撞了怎么办。他说:无外乎就一起发,但是要看什么?

    • 谁能把相同的 idea 讲的更清楚,创新点挖掘的更新颖,更透彻?
    • 谁能设计好实验,将实验和数据做的更扎实,更具有论述能力?
    • 谁能更好地泛化针对这个 idea 提出来的方法,如果是工程化的能够更好地落地?

    最后我的博士学长也跟我说:“不要内耗,先去相信自己的工作更加 solid,先去做了再说!”(虽然最后那份工作我们没有别人做的好所以被 reject 了 🥺)

    祝 lz 成功~

    Post #110 ❤️ 4 likes
  • C++ 在写不好的情况下,重载决议出现二义性导致的不可移植性。。。

    armv7(32 bits) clang
    arm64(64 bits) clang
    RISC-V(32 bits) clang
    RISC-V(64 bits) clang

    原因如下:

    在 64 位系统上,ptrdiff_t 应该是 longstd::size_t 也应该是unsigned long, 这样就会出现

    • Candidate 1: char& BadString::operator[](unsigned long): 参数一是隐藏的 BadString& 完美匹配,参数二是 unsigned long 需要标准转换;
    • Candidate 2: operator[](char*, long): 参数一需要用户定义的转换,参数二需要标准转换;

    这样不可能出现歧义,应该是 32 位系统上,此时 ptrdiff_t 定义为 int, std::size_t 定义为 unsigned int. 这样我们有:

    • Candidate 1: char& BadString::operator[](unsigned int): 参数一是隐藏的 BadString& 完美匹配,参数二是 unsigned int 需要标准转换;
    • Candidate 2: operator[](char*, int): 参数一需要用户定义的转换,参数二完美匹配;

    从而出现不符合重载决议中的:“有且仅有一个候选函数,所有参数的匹配都不比其他函数差”的情况,出现二义性

    好像不知道为什么被标记了,修改重发一下

    例子来自: 《C++ templates The complete guide, 2nd Edition》,Page 685

    Post #296 ❤️ 2 likes
  • 上海今天上午下雪很大~

    b3dbd79a1543973813a6a3f816557a66|374x500
    0cfceae300608d4741b2e448dd36f2a4|666x500
    ff991c9607db9809c7c140323b8df855|666x500
    1ff71929435d2312500a04ed32f4c15c|374x500

    Post #295 ❤️ 5 likes
  • 突然发现自己之前的计划,只完成了

    看完 cuda by examples
    读 sigcomm25 的论文

    好惭愧啊 🥺

    Post #294 ❤️ 6 likes
  • 好久没在门内说话了,最近这几个月实在是太忙了。

    首先还是不断地在学习新的东西,重点了解 MoE 中的 alltoall 通信,包括了 dispatch 和 combine 两部分。在这短短的几个月,关注到了 MoE 在 Infra 下的不同通信库的实现,主要都是针对 DeepEP 工作的改进和兼容:

    • pplx-garden 将 p2p 通信通过 rust 实现,部署了在 AWS 上 EFA 网卡的 MoE 通信;
    • NCCL 发布了 GPU-Initiated-Network,实现了任意 N 卡 + 任意网卡的灵活的 MoE 通信;
    • UCCL-EP 则更进一步实现了任意 GPU+ 任意网卡的灵活的 MoE 通信;

    在了解大佬们工作的过程中,也越发了解到 DeepEP 都是这些工作的来源,也更感慨于自己对于 DeepEP 的不了解和无知。在粗略地读了一小部份的代码后,更惊叹于 DeepEP 内非常细致的针对各个方面的优化:

    • 对于 buffer 在不同状态下空间的计算,有下图所示:

    image|690x296

    • 在 HT 模式下和 LL 模式下采用不同数量的 QueuePair 实现网络层面的并行
    • 在数据搬运的方式下采用 AsyncCopy(Ampere 引入的特性)和 TMA 操作(二维异步数据搬运,Hopper 卡引入的特性),这一点仍然需要继续学习 CUDA 才能更加了解
    • 细致规定了每个 SM 的调度和使用,将计算和通信融合在同一个核心内
    • NVSHMEM 借用 OpenSHMEM 的单边操作和 PGAS 内存模型提供 GPU Direct Async Kernel Initiate 支持

    可以说这方面对于アイラちゃん来说更是无底洞,甚至连 C++/Cuda 基础都无法支持アイラちゃん继续探索和学习。因此 C++ 语言和 Cuda 异构基础仍然是需要常看常学的部分,尤其是异步模型和 SIMT 模型。

    除此之外,最近帮助学长刚刚跑完了一份即将投稿 ToN 的实验数据,也是老生常谈的 ns3 网络仿真,这个上古的老库还是这么地折磨人。也因为这一部分差不多也告一段落(アイラちゃん的部分)才刚好可以暂时休息休息,来门内写下一长串话。

    这几个月也帮助实验室做了不少 infra 方面的支持,比如各种通信库的适配,vllm 在二机四卡下的部署,点亮 BF3 网卡实现简单的逻辑卸载,DOCA 的 GPUNetIO(NVSHMEM 实现 GDAKI)的基础的简单了解。后面如果可能有机会也会探索一下 KVCache 在 DPU 上的卸载,不过看起来非常的困难,至少可以说是毫无头绪。。。。。。

    接下来想要做的就是:看一些 C++/Cuda 的书,看 DeepEP 通信库,了解一些优秀的开源项目,学习别人的设计思想。一来是更了解语言提升自己程序能力的上限,二来是想要更跟进现在 Infra 的发展(实在太快了),三来是想要学会怎样应对模糊的需求,设计更合理的程序架构,减少工作量,了解一下他人的设计思想。

    好久没有在门内说话了,门内的伙伴们还好吗?谢谢你们拨冗看完这一长串アイラちゃん的牢骚话,祝大家新的一年快快乐乐,事业有成~

    (P.S. 魔法少女的魔女裁判真的好好玩,趁着冬促买下来了,桜羽エマ大好き!)
    @tori_nankotsu96
    image|339x500

    Post #293 ❤️ 8 likes
  • 感觉好古老的东西
    现在还用 PPPoE 拨号上网吗?好像是我小学时候采用的东西了
    是不是用一个叫“调制解调器”的东西 😇

    Post #6
  • 会赢的!!!

    Post #159 ❤️ 3 likes
  • 没事我学 50 音图快几个月了也背不下来
    抄一份

    れ 写烂了 🥺

    8893b1777dbc11f627b8e235a93d9722|690x406

    Post #8 ❤️ 3 likes
  • 编译了一下 NCCL。u1s1 clangd 与 nvcc 的相性太差,不能很好滴起到 LSP 的作用。
    然后准备开始调研 ResCCL 和 SyCCL(都没有开源),并且更加细致地了解 all-reduce, all-gather, all-to-all 等通信源语底层的实现(ring,half-doubling 等等)。

    双选开始了,方向完全就是数字 IC/模拟 IC。我实在是还没有想好该往哪个方向走,有点小纠结。等到下周再联系提供的导师看看。虽然电路/数电/模电/计算机体系结构(自学)/信号与系统/数字信号处理(自学)都学过,但是对于 CMOS/VLSI 这种完全没有任何经验。也用 verilog 写过经典 5 级流水线(填空式),和一个 tinyTPU 但是对这一方面完全没有任何经验,有点小纠结。

    当然直到大四结束前都还留在原先的组里工作(学长的挽留?可能是暑假里对于仿真系统的开发挺出力的缘故),因此应该也还会对 cuda/CCL 有进一步的了解吧。未来会怎样呢我也不知道。

    曾经玩过的“你和她和她的恋爱”这部 gal 里面有这样一段话:

    岁月的流逝,就如同层层向上的楼梯。一旦对此产生了任何疑问,那么从那一刻起,跨上台阶的步伐将会越发沉重起来。

    所以,要一步,一步,什么都不去想,就这么向上攀登。

    因为是楼梯,所以也就没有分支。

    “那个时候要是这么做就好了“之类的后悔是没有意义的。

    而什么“人生是一连串的选择”,其实是在失败之后,人们回首往事时出于后悔而发出的,无用的感叹。向着紧接着的过去,理所当然存在着的明日。

    一步,一步,向前迈进。


    昨天刷 B 站看到 三秋 缒 (zhui,四声) 的《三日间的幸福》,立刻去图书馆找了一本,看了之后虽然觉得剧情有些地方转折过快,有些突兀,但是体现了作者对人生的思考,和对未来的期望。有些段落我特别喜欢。为了不剧透,进行了模糊处理~

    段落一:
    “大部分的失败者只会说什么如果人生能够重来,一定能创造不凡的成就,自以为在尝过人生苦楚之后,就不会再重蹈覆辙了。可是包括我,这种失败者根本就搞不清楚状况。失败的人的确十分了解何谓失败,但了解失败与了解成功完全是两码子事,就算挽回失败,也不代表就能迈向成功,因为夹在两者之间的终究只是一处灰暗的起跑点,可惜失败者们向来不了解这点。”

    段落二:
    “大多数的人只有在面临死亡之后,才会懂得活着多么可贵,也才能找回原有的活力,然后陷入『过去虽然百般荒唐,但如今懂得自省的我,应该还能做些什么吧?』的迷思,不过这些人都犯了要命的自以为是,因为他们不过是好不容易站回起跑点而已。简单来说,就是在输得一塌糊涂之后,才总算冷静地看待眼前的赌局。”

    段落三:
    “这世界绝不会突然对死到临头的人变得亲切。恐怕,这世界只对已死的人温柔。早就熟知这一切,却又耽溺于天真想法的我,心底深处果然还是存着一份全世界能瞬间变得美好的期盼啊。”

    《三日间的幸福》这本作品非常慢热,但是在不现实的剧情中却总是具有非常强烈的现实感,这种真假之间的交织很容易给人一种“假作真时真亦假”的虚幻感,也很容易让染不自觉地代入到生活重复的男主身上。但是随着作品到达中期和后期,节奏陡然加快,感情线也随之剧烈跌宕起伏。爆发式的感情变化与人物的蜕变会让读者有欲罢不能的感觉——快节奏的同时能够快速地推进剧情。即使有些剧情转折过快,略显突兀,但仍然瑕不掩瑜,也能让男女主之间的感情直接叩击读者的内心,是一本非常不错的作品~

    Post #292 ❤️ 4 likes
  • 热阿线狂堵 12km,提前抽身掉头走国道 😭
    美食:🐑,🐑的各种做法,奶,奶的各种做法 😋

    Post #290 ❤️ 2 likes
  • 竟然翻墙才能来了,好久不更新了。

    最近正在进行:

    GPU 的学习,参考的是《cuda by examples》
    CMU15445,primer 部分刚刚完成,修复了一些 cmake 版本的问题,并且完成简单的并发跳表部分,后面会缓慢地做 lab(时间太少了)
    读 sigcomm25 的论文,已经读了 stellar,接下来看看 ResCCL 和 syCCL
    学习《数学物理方法》到解析函数部分。

    很快也要双选了,主要做的都是数字芯片或模拟芯片方向,所以慢慢地也要复习数电和模电一下,然后到时候在问一问导师应该补上哪些基础知识吧。


    读论文的时候还是深感知识的欠缺和稀少,还是要多学。最后附上一张在 autodl 上租来的 4090 画出来的 5000x5000 的 julia set~

    6185051e92986fbde8b4900064f76b31|500x500


    国庆做了什么?去内蒙古玩了。但是到了景点就想睡觉。同学指定的旅游计划太折磨人了,仗着我有 C1 驾照,并且寒假开过车,就制定了雄心勃勃的旅游计划,一天开车的公里数到达了 600km。整个假期下来,アイラちゃん几乎驾驶了接近 1600km,国道,省道,乡道,高速,夜间公路,土路,草原...... 什么地方都有着驾车过的轨迹~下次旅行决定最多一天开 300km,不然太痛苦了 😭

    32154dbf53b37a9c04377fcd43ab270f|226x500


    一些照片

    照片

    4864b249dc526dd4d9298236e735a048|666x500
    9e2a893015bc36b0bbc6eab03fc70f49|666x500
    367baac7c3377f28017140911c7bcf5d|666x500
    67567f59091fb52c0f1c1c6f34a1aeb1|666x500
    1b5d2d2ee4fa94efb10fe6ad2e5ffcd1|666x500

    Post #288 ❤️ 8 likes
  • 2025 年 9 月 29 日
    今天 deepseek 发布了 v3.2 试验版。论文很短,只有 6 页。因此アイラちゃん窃读之,并向塬友们略作分享。愿塬友们批评指正!

    更好的阅读体验:

    https://www.cnblogs.com/mumujun12345/p/19119720

    节前发版:Deepseek v3.2 exp 之打工人的悲鸣

    加班快乐...

    论文原文

    推理代码

    架构

    与 Deepseek-V3.1 相比,新一般的架构更改仅仅在后续训练中引入了新的稀疏注意力机制 DSA。

    DSA:deepseek 稀疏注意力

    主要包括两个部分:一个 ligtning indexer(索引器)和一个细粒度的 token 选择机制。

    Lightning indexer

    Step 1: 计算索引分数

    计算了 当前询问 Q token htRdh_t\in \mathbb{R}^d 与一个 前序 token hsRdh_s\in\mathbb{R}^d 的索引分数,决定了 Qtoken 将会选择哪一个 token。

    It,s=j=1HIwt,jIReLU(qt,jIksI) I_{t,s}=\sum\limits_{j=1}^{H^I}w_{t,j}^I\cdot\text{ReLU}\left(q_{t,j}^I\cdot k_s^I\right)

    其中我们有:

    • HIH^I 索引头的数目。

    • qt,jIRdIq_{t,j}^I\in\mathbb{R}^{d^I}wt,jIRw_{t,j}^I\in\mathbb{R} 从 Q token hth_t 中导出。

    • ksIRdIk_s^I\in R^{d^I} 从前序的 hsh_s 中导出。

    作者选择了ReLU来提升吞吐率。即使 lightning indexer 仅有很少数量的头并且可以在 FP8 上部署,其计算效率也是非常显著的。

    Step 2: 选择前 k 个索引分数最高的 csc_s, 计算注意力输出

    给定了索引分数 It,sI_{t,s},我们的细粒度 token 索引机制将会仅仅取出那些具有前 k 个索引分数的 token。随后,注意力输出 oto_t 将会在当前 Q token hth_t 和稀疏化选出的 csc_s 中进行

    csc_s 其实是 MLA 中低秩投影计算出来的向量,用于减少 KVCache 的存储开销,提高推理效率。

    ut=Attn(ht,{csIt,sTop-k(It,:)}) u_t = \text{Attn}(h_t,\{c_s\,|\, I_{t,s}\in\text{Top-k}(I_{t,:})\})

    下面是新旧结构的对比。上图为新的结构。下图为曾经的旧结构。

    6|690x407

    截屏 2025-09-30 00.43.35|690x468

    在 MLA 下实例化 DSA

    为了考虑从 v3.1 继续训练,需要基于 MLA 上实例化 DSA。在 kernel 层面,每一个 KV 项都需要在多个查询之间共享,提升计算效率。因此,我们在 MLA 的 MQA 模式上部署了 DSA。这样每一个**潜在层 (latent vector)**将会在每个头之间共享(多个头共用一个潜在向量 cic_i,也就是多个头——多个 Query,共用一个 KV)。

    截屏 2025-09-30 00.32.12|690x373

    训练

    从 v3.1-Terminus 后继续训练,上下文长度扩展到 128K。

    Step 1: 稠密 warm-up 阶段

    用于初始化 lightning indexer。继续保持稠密注意力机制,其余参数全部冻结,仅剩下 lightning indexer 进行训练。

    为了保持 indexer 输出与原先的主要注意力分布对齐,对于第 t 个查询 token,我们首先将多个头主要注意力分数进行相加,然后在序列维度上进行 L1-正则化,生成目标分布 pt,:Rtp_{t,:}\in\mathbb{R}^t. 基于 pt,:p_{t,:}, 我们设置一个 KL-散度 loss 作为我们训练 indexer 的优化目标。

    LI=tDKL(pt,:Softmax(It,:)) \mathcal{L}^I = \sum\limits_{t}\mathbb{D}_{KL}\left(p_{t,:}||\text{Softmax}(I_{t,:})\right)

    作者声称采用了 10310^{-3} 的学习率训练了 1000 步。每一步具有 128K 长度的 16 个序列,总共 2.1B 个 token。

    Step 2: 稀疏训练阶段

    在进行稠密训练之后,进入到了细粒度的 token 选择,并以此来优化整体模型的参数,来获得 DSA 的稀疏模式。在这一阶段,我们不在选择所有的 token,而是通过上文的方式选择通过 indexer 判断出来的,索引分数最大的 K 个 token:

    St={sIt,sTop-k(It,:)}\mathcal{S}_t=\{s\,|\,I_{t,s}\in\text{Top-k}(I_{t,:})\}

    LI=tDKL(pt,St:Softmax(It,St)) \mathcal{L}^I = \sum\limits_{t}\mathbb{D}_{KL}\left(p_{t,S_t:}||\text{Softmax}(I_{t,S_t})\right)

    需要值得注意的是我们将 indexer 的输入从计算图中分离,也就是分开 indexer 和 DSA 的其他部份,分别进行优化。

    • indexer 仍然仅仅根据 LI\mathcal{L}^I 进行优化。

    • 其他部分通过模型其他部分的 loss 进行优化。

    稀疏训练采用学习率 7.3×1067.3\times 10^{-6},每个 query 选择 2048 个 KV token。训练 15000 步,具有 480 个长度为 128K 的 token,总共是 943.7B token 数量。

    Step 3: 后训练

    后训练与先前 deepseek-v3 的后训练类似,主要有两步:

    1. 专家知识蒸馏。

    2. 混合 RL 训练。

    专家知识蒸馏

    • 对于每个任务我们都训练了一个专门的针对这个领域知识的模型,这些模型都是从相同的预训练 v3.2 基座模型的 ckpt 而来。

    • 针对写作任务和通用问答任务,我们划分了 5 个领域:数学,竞赛类编程,通用因果逻辑,多智能体编码,多智能体搜索。

    • 对于每个专家,我们都通过大规模强化学习方式进行训练。

    • 并且,我们部署了不同的模型来生成针对思维链 (CoT) 的训练数据,以及直接回答 (非思维链模式) 的训练数据

    • 当专家模型完成后,他们将被用于为最后的 ckpt 生成领域专用的知识。最终 ckpt 在各个领域与专家模型的差距将通过后续的强化学习来进行弥补。

    混合强化学习

    • 与 v3.1 相同,仍然采用的是 GRPO 强化学习方式。

    • 与前面分不同阶段强化学习不同的是,作者将**多个阶段的 RL 学习 (因果,智能体,人类对齐训练)**混合到了一起。

    • 优势是可以讲多个领域的表现有效进行平衡并且设法克服在多阶段训练中造成的灾难性遗忘问题

    • 对于因果智能体任务,我们部署了基于规则的结果奖励长度惩罚以及语言一致性奖励

    • 对于生成式任务,我们部署了一个生成式奖励模型,将按照自己的规则进行评估。

    • reward 进行了两方面的权衡:(1) 长度 vs 准确度。(2) 一致性 vs 准确度。

    评估结果

    • 推理开销从原先的 O(L2)O(L^2) (原先需要计算所有的 token,长度为 L) 变成 O(Lk)O(Lk) (Q token 长度不变,但是 KV 低秩投影 token 通过 lightning indexer 选择 K 个)。对于 lightning indexer,其计算复杂度仍然为 O(L2)O(L^2),但是因为其具有的头数量比原先的 MLA 头数量少,因此常数因子的减少也显著提升了其计算效率。

    8|492x500

    Post #287 ❤️ 4 likes
  • 对的,如果 A 是稳定的那么他肯定是极大值,A+1 到 BC 和 A-1 到 BC 的距离都比他小

    アイラちゃん,post:279, topic:14653, username:sumire:
    • 顺时针:观察 A 顺时针方向的下一个节点 A+1到达 BC 的距离是否更大。如果更大说明 A 不稳定。A <- A+1
    • 逆时针:观察A 逆时针方向的下一个节点 A-1到达 BC 的距离是否更大。如果更大说明 A 不稳定。A <- A-1

    更改了一下说法

    Post #286 ❤️ 1 like
  • 加油,祝一切顺利呀~

    Post #47 ❤️ 5 likes
  • 截屏 2025-09-27 22.57.07|690x292

    CGTN 这稿子写的好呀,学习了~

    https://news.cgtn.com/news/2025-09-26/Chinese-premier-speaks-at-general-debate-of-80th-session-of-UNGA-1GZjW6vuPsI/p.html

    "任何关心世界局势的人都会想问:为何我们人类在历经磨难后,不能秉持更大的良知与理性,以善意相待,和平共处?"李说道。

    "面对人道灾难等不堪事件,我们怎能对公然践踏公平正义的暴行视而不见、袖手旁观?面对肆无忌惮的霸权霸凌行径,我们怎能因畏惧强权而沉默顺从?"李诘问道。

    "我们又怎能任由联合国创始先辈们的热血丹心,就这样湮没于历史书页?"李补充道。

    Post #283 ❤️ 5 likes
  • 没有多少人愿意搞 sys 和 arch 了,就算搞也是去搞 MLsys 或者 AI accelerator,自然想进 ipads 的人就少了。因为不想搞基础研究,出成果慢且困难。

    アイラちゃん暑假就期望能帮学长发一篇纯 RDMA datacenter 的 sigcomm,但是两个月过去了才仿真出了 baseline(ns3, C++14)。所以最后也是无疾而终...

    Post #314 ❤️ 2 likes
  • 手写的~因为几何部分手写比打 latex 快很多

    Post #281 ❤️ 4 likes
  • 因为今天的题目很有意思所以特别想跟大家分享一下。

    812. 最大三角形面积

    一开始我想到了凸包,然后想到凸包后可以采用 O(n2)O(n^2) 的渐进算法算出最大面积。但是灵神的回答中提到了一篇论文!

    maximal-area-triangles-in-a-convex-polygon-4k6l0fx7vg(1).pdf|attachment (2.5 MB)

    作者声称我们可以通过 O(n)O(n) 的算法复杂度找到这个最大值。

    于是我就写出了下面的这一大段💩山代码

    C++
    class Solution {
    public:
        double largestTriangleArea(vector<vector<int>> &points) {
            int n = points.size();
            int *stack = new int[n + 2];
            memset(stack, -1, sizeof(int) * (n + 2));
            bool *used = new bool[n];
            memset(used, false, sizeof(bool) * n);
            ranges::sort(points, {}, [](const vector<int> &p) -> tuple<const int &, const int &> {
                return tie(p[0], p[1]);
            });
            // lower convex hull.
            int stack_pt = 0;
            for (int index = 0; index < (int)points.size(); index++) {
                int x = points[index][0], y = points[index][1];
                // When \vec{StSt-1 x StP <= 0} and more than 2 elements in stack: pop
                while (stack_pt + 1 > 2) {
                    int top1 = stack[stack_pt], top2 = stack[stack_pt - 1];
                    int x1 = points[top1][0], y1 = points[top1][1];
                    int x2 = points[top2][0], y2 = points[top2][1];
                    if ((x1 - x2) * (y - y1) - (y1 - y2) * (x - x1) >= 0) {
                        used[stack[stack_pt]] = false;
                        stack_pt--;
                        continue;
                    } else {
                        break;
                    }
                }
                if (used[index] == false) {
                    stack[++stack_pt] = index;
                    used[index] = true;
                    continue;
                }
            }
            // upper convex hull.
            int lower_size = stack_pt;
            for (int index = (int)points.size() - 1; index >= 0; index--) {
                int x = points[index][0], y = points[index][1];
                // When \vec{StSt-1 x StP <= 0} and more than 2 elements in stack: pop
                while (stack_pt > lower_size) {
                    int top1 = stack[stack_pt], top2 = stack[stack_pt - 1];
                    int x1 = points[top1][0], y1 = points[top1][1];
                    int x2 = points[top2][0], y2 = points[top2][1];
                    if ((x1 - x2) * (y - y1) - (y1 - y2) * (x - x1) >= 0) {
                        used[stack[stack_pt]] = false;
                        stack_pt--;
                        continue;
                    } else {
                        break;
                    }
                }
                if (used[index] == false) {
                    stack[++stack_pt] = index;
                    used[index] = true;
                    continue;
                }
            }
            // Calculate the triangle by calculate the convex hull point.
            auto S = [](int x1, int y1, int x2, int y2, int x3, int y3) -> double {
                return 0.5 * abs(x1 * y2 + x2 * y3 + x3 * y1 - x1 * y3 - x2 * y1 - x3 * y2);
            };
            auto dist = [&](int a, int b, int c) {
                int xa = points[stack[a]][0], ya = points[stack[a]][1];
                int xb = points[stack[b]][0], yb = points[stack[b]][1];
                int xc = points[stack[c]][0], yc = points[stack[c]][1];
                return (yb - ya) * xc + (xa - xb) * yc;
            };
            auto area = [&](int a, int b, int c) -> double {
                int x1 = points[stack[a]][0], y1 = points[stack[a]][1];
                int x2 = points[stack[b]][0], y2 = points[stack[b]][1];
                int x3 = points[stack[c]][0], y3 = points[stack[c]][1];
                return 0.5 * abs(x1 * y2 + x2 * y3 + x3 * y1 - x1 * y3 - x2 * y1 - x3 * y2);
            };
            double ans = 0;
            stack[0] = stack[stack_pt], stack[stack_pt + 1] = stack[1];
            if (stack_pt == 3) {
                int x1 = points[stack[0]][0], y1 = points[stack[0]][1];
                int x2 = points[stack[1]][0], y2 = points[stack[1]][1];
                int x3 = points[stack[2]][0], y3 = points[stack[2]][1];
                return S(x1, y1, x2, y2, x3, y3);
            }
            // Find one 3 stable.
            auto findOne3Stable = [&]() -> tuple<int, int, int> {
                double max_S = 0.0;
                int a = 1, b = 2, c = 3;
    
                int C = c;
                for (int B = b; B < stack_pt; B++) {
                    if (C == B) C++;
                    while (C < stack_pt && dist(1, B, C + 1) > dist(1, B, C) + 1e-6) {
                        C++;
                    }
                    double new_area = area(1, B, C);
                    if (new_area > max_S) {
                        max_S = new_area;
                        a = 1;
                        b = B;
                        c = C;
                    }
                }
                // Step 2: find one 2-stable.
                while (dist(b, c, a + 1) > dist(b, c, a) + 1e-6) {
                    a = a % stack_pt + 1;
                    bool flag = true;
                    while (flag == true) {
                        flag = false;
                        while (dist(c, a, b + 1) > dist(c, a, b) + 1e-6) {
                            b = b % stack_pt + 1;
                            flag = true;
                        }
                        while (dist(a, b, c + 1) > dist(a, b, c) + 1e-6) {
                            c = c % stack_pt + 1;
                            flag = true;
                        }
                    }
                }
                // Step 3: find one 3-stable.
                while (dist(b, c, a - 1) > dist(b, c, a) + 1e-6) {
                    a--;
                    if (a == 0) a = stack_pt;
                    bool flag = true;
                    while (flag == true) {
                        flag = false;
                        while (dist(c, a, b - 1) > dist(c, a, b) + 1e-6) {
                            b--;
                            if (b == 0) b = stack_pt;
                            flag = true;
                        }
                        while (dist(a, b, c - 1) > dist(a, b, c) + 1e-6) {
                            c--;
                            if (c == 0) c = stack_pt;
                            flag = true;
                        }
                    }
                }
                return {a, b, c};
            };
            auto RotateAndKill = [&](int a, int b, int c) -> double {
                int ans_a = 1, ans_b = 2, ans_c = 3;
                double max_a = 0.0;
                int r = a, t = c;
                // Section 4:
                while (b != t || c != r) {
                    // repeat 1. find A.
                    while (dist(b, c, a + 1) > dist(b, c, a)) {
                        a = a % stack_pt + 1;
                    }
                    double new_area = area(a, b, c);
                    // output the 3-stable.
                    if (new_area > max_a) {
                        max_a = new_area;
                        ans_a = a, ans_b = b, ans_c = c;
                    }
                    // Repeat 2: Calculate Ib,c.
                    double dx1 = points[stack[b]][0] - points[stack[b + 1]][0], dy1 = points[stack[b + 1]][1] - points[stack[b]][1];
                    double dx2 = points[stack[c]][0] - points[stack[c + 1]][0], dy2 = points[stack[c + 1]][1] - points[stack[c]][1];
                    double delta = dy1 * dx2 - dy2 * dx1;
                    if (delta >= -1e-9)
                        b = b % stack_pt + 1;
                    else {
                        double U = dy1 * points[stack[c]][0] + dx1 * points[stack[c]][1];
                        double V = dy2 * points[stack[b]][0] + dx2 * points[stack[b]][1];
                        double Ix = (U * dx2 - V * dx1) / delta;
                        double Iy = (V * dy1 - U * dy2) / delta;
                        // (yc - yb) * xa + (xb - xc) * ya;
                        if (dist(b, c, a) > (points[stack[c]][1] - points[stack[b]][1]) * Ix + (points[stack[b]][0] - points[stack[c]][0]) * Iy) {
                            c = c % stack_pt + 1;
                        } else {
                            b = b % stack_pt + 1;
                        }
                    }
                }
                a = ans_a, b = ans_b, c = ans_c;
                return max_a;
            };
            // Step 1:
            auto [a, b, c] = findOne3Stable();
            ans = RotateAndKill(a, b, c);
            return ans;
        }
    };
    

    我们姑且不管数学方面是否正确,我们来尝试部署一下这个算法。

    前置知识

    • stable:对于一个已经提供了凸多边形点集的三角形子集 ABC,A 是 stable 的,当且仅当在固定了 BC 后,A 距离 BC 的距离是最大的。
    • 我们所有的点将是顺时针的。即凸包上的点按照顺时针进行编号。

    接下来我们来看算法是如何实现的。

    • 找到一个 3-stable 三角形。也就是说我们首先要找到一个三角形 ABC,满足 A,B,C 都是 stable 的条件。
      • 固定一个 A,然后寻找 B,C。这样我们就可以得到一个以 A 为根,B,C stable 的三角形。我们通过枚举 B,判断 C 是否是 stable 的。因为每次枚举 B,我们无需将 C 从头开始枚举,只需要从上一次的点继续向下枚举。因此摊还分析为 O(1) 平均操作(这一部分可以在官解上看到相同的做法)。对于 B 的枚举就是 O(n)O(n)。于是我们有:
    findOne3stable - part1
    double max_S = 0.0;
                int a = 1, b = 2, c = 3;
    
                int C = c;
                for (int B = b; B < stack_pt; B++) {
                    if (C == B) C++;
                    while (C < stack_pt && dist(1, B, C + 1) > dist(1, B, C) + 1e-6) {
                        C++;
                    }
                    double new_area = area(1, B, C);
                    if (new_area > max_S) {
                        max_S = new_area;
                        a = 1;
                        b = B;
                        c = C;
                    }
                }
    
    • 接着,作者假设了这个 A 并不是一个 stable 点。因此需要继续进行枚举,不断枚举根节点 A 来判断这个点是不是 stable 的。这里用了一个小 trick。我们的 stack 记录在 [1...stack_pt] 中,向外延拓两点:stack[0] = stack[stack_pt]以及 stack[stack_pt+1]=stack[1] 来规避掉错误的地址访问。这样我们可以尝试讨论 A 使它成为 stable 点。我们从两个方向来分别判断它是不是稳定的
      • 顺时针:观察 A 顺时针方向的下一个节点 A+1到达 BC 的距离是否更大。如果更大说明 A 不稳定,A <- A+1
      • 逆时针:观察A 逆时针方向的下一个节点 A-1到达 BC 的距离是否更大。如果更大说明 A 不稳定。A <- A-1

    这里距离可以直接采用化简后的叉乘公式。我们可以回顾一下。这里我们统一为顺时针。固定住 AB,我们有:

    叉乘化简

    笔记 2025 年 9 月 25 日|690x316

    这样可以让数值更加稳定。

    findOne3Stable - part2
    // Step 2: find one 2-stable.
                while (dist(b, c, a + 1) > dist(b, c, a) + 1e-6) {
                    a = a % stack_pt + 1;
                    bool flag = true;
                    while (flag == true) {
                        flag = false;
                        while (dist(c, a, b + 1) > dist(c, a, b) + 1e-6) {
                            b = b % stack_pt + 1;
                            flag = true;
                        }
                        while (dist(a, b, c + 1) > dist(a, b, c) + 1e-6) {
                            c = c % stack_pt + 1;
                            flag = true;
                        }
                    }
                }
                // Step 3: find one 3-stable.
                while (dist(b, c, a - 1) > dist(b, c, a) + 1e-6) {
                    a--;
                    if (a == 0) a = stack_pt;
                    bool flag = true;
                    while (flag == true) {
                        flag = false;
                        while (dist(c, a, b - 1) > dist(c, a, b) + 1e-6) {
                            b--;
                            if (b == 0) b = stack_pt;
                            flag = true;
                        }
                        while (dist(a, b, c - 1) > dist(a, b, c) + 1e-6) {
                            c--;
                            if (c == 0) c = stack_pt;
                            flag = true;
                        }
                    }
                }
    

    这样就找到一个 3-Stable 了。

    接下来进入到寻找所有的 stable 三角形。作者的做法是:

    截屏 2025-09-27 18.47.16|690x329

    最关键的部分是Ib,cI_{b,c}。我们发现 Ib,cI_{b,c} 的选取方式是如下图所示的:

    截屏 2025-09-27 18.47.54|690x381

    • 对于 C 点,做一条射线与 B 点和 B 的顺时针下一个点 B+1 的方向相反。
    • 对于 B 点,做一条射线与 C 点和 C 的顺时针下一个点 C+1 的方向相同。

    射线的交点就是我们的 Ib,cI_{b,c}。这里的交点怎么求取呢?

    • 平行的时候,B+1,B\vec{B+1, B}C+1,C\vec{C+1,C} 叉乘为 0.我们将交点 Ib,cI_{b,c} 与 BC 之间的距离定为无穷大。
    • 根据平行叉乘为 0,以及克莱默公式有:

    笔记 2026|585x500

    • 通过面积最大值来得到 3-stable 三角形。

    于是我们有:

    RotateAndKill
    auto RotateAndKill = [&](int a, int b, int c) -> double {
                int ans_a = 1, ans_b = 2, ans_c = 3;
                double max_a = 0.0;
                int r = a, t = c;
                // Section 4:
                while (b != t || c != r) {
                    // repeat 1. find A.
                    while (dist(b, c, a + 1) > dist(b, c, a)) {
                        a = a % stack_pt + 1;
                    }
                    double new_area = area(a, b, c);
                    // output the 3-stable.
                    if (new_area > max_a) {
                        max_a = new_area;
                        ans_a = a, ans_b = b, ans_c = c;
                    }
                    // Repeat 2: Calculate Ib,c.
                    double dx1 = points[stack[b]][0] - points[stack[b + 1]][0], dy1 = points[stack[b + 1]][1] - points[stack[b]][1];
                    double dx2 = points[stack[c]][0] - points[stack[c + 1]][0], dy2 = points[stack[c + 1]][1] - points[stack[c]][1];
                    double delta = dy1 * dx2 - dy2 * dx1;
                    if (delta >= -1e-9)
                        b = b % stack_pt + 1;
                    else {
                        double U = dy1 * points[stack[c]][0] + dx1 * points[stack[c]][1];
                        double V = dy2 * points[stack[b]][0] + dx2 * points[stack[b]][1];
                        double Ix = (U * dx2 - V * dx1) / delta;
                        double Iy = (V * dy1 - U * dy2) / delta;
                        // (yc - yb) * xa + (xb - xc) * ya;
                        if (dist(b, c, a) > (points[stack[c]][1] - points[stack[b]][1]) * Ix + (points[stack[b]][0] - points[stack[c]][0]) * Iy) {
                            c = c % stack_pt + 1;
                        } else {
                            b = b % stack_pt + 1;
                        }
                    }
                }
                a = ans_a, b = ans_b, c = ans_c;
                return max_a;
            };
    

    真的很有意思。花了我接近 6 个小时。

    Post #279 ❤️ 6 likes
  • It seems that people in United States act like this. They participate in multiple part-time jobs in order to avoid the 8-hour job regulations, while trying to feed their families through these part-time jobs. In my opinion, taking multiple part-time jobs will put you under much more weary and trivial jobs that will obviously cripple your interests to life, which is far from Work-Life-Balance, or gaining achievements while feed yourself or family under that circumstance.

    看起来在美国很多人都是这么做。他们参与多个兼职工作以规避 8 小时工作制,通过这些兼职工作来养活他们的家庭。我认为,参与多项兼职工作将会使你陷入疲劳琐碎的工作中,显然会摧残你对生活的兴趣。在这样的条件下将与生活 - 工作平衡、或者在养活自己/家庭的目标完全背道而驰。

    Post #29 ❤️ 4 likes
  • 欢迎加入
    🐷
    大家庭~

    Post #276 ❤️ 5 likes
  • 今天学习数学物理方法到解析函数,随后一直摸鱼
    发现没有 ddl 自己就会懒下来。。。

    Screenshot_20250926_204528|226x500

    继续恢复训练,今天是 4km/5min 配速,希望能慢慢恢复到大二学年的体能状态。。。

    为什么要先跑一圈外圈呢,因为跑道上人很多,需要稳定自己的速度后再慢慢切到内圈。。。

    最后抱怨一句:亲爱的上海市,您还要热到什么时候呢 😇

    Post #274 ❤️ 4 likes
  • 谢谢树莓派老师
    一起加油!

    Post #273 ❤️ 1 like
  • 放松一下,国庆好好玩
    把烦恼丢给国庆后的自己吧

    Post #278 ❤️ 5 likes
  • Z 一串真的很厉害
    祝 Z 一串前程似锦

    Post #302 ❤️ 1 like
  • 我不行的,不然也不会这么累
    只有拿烂命狠干才能达到普通水平 🥺

    Post #271 ❤️ 2 likes
  • 不过这个暑假还是有一点收获的
    给 mooncake 做了一个 CI/CD 的 PR,他们的动态库在打包的时候没有移除干净,对于 python3.8 的 patchelf,无法移除具有通配符的动态库文件,这样 python3.8 在使用该库的时候可能会污染本地环境,带入不必要的动态库。
    也算是尽自己的一点微不足道的“螺丝钉”能力赚了一个 PR(跟白捡似的 :tieba_hehe:

    https://github.com/kvcache-ai/Mooncake/pull/801

    Post #269 ❤️ 5 likes
  • xm Z 一串~
    アイラちゃん比较社恐不太喜欢去漫展 😵‍💫

    Post #298 ❤️ 1 like
  • 最后兜兜转转跨保到集成电路工程硕士了,这下不得不补很多专业知识了...
    今天就这样吧,后面要补上的知识还有很多

    Today Learning

    51 单片机学习:初识 51

    参考书籍:《新概念 51 单片机 C 语言教程入门提高开发拓展全攻略第 2 版》,作者:郭天祥

    芯片初识

    自己买的单片机是 STC89C52RC 40IPDIP40. 可以在下图中看到拓印在芯片上的标号。

    1|690x255

    相关名称的解释如下:

    • STC:前缀,表示芯片 STC 公司生产的产品。其他前缀还有如 AT、i、Winbond、SST 等。
    • 8:表示该芯片为 8051 内核芯片。
    • 9:表示内部含 Flash E2PROM 存储器。另,80C51 中的 0 表示内部含 Mask ROM(掩模 ROM)存储器,87C51 中的 7 表示内部含 EPROM 存储器(紫外线可擦除 ROM)。
    • C:表示该器件 CMOS 产品。另,89LV52 和 89LE58 中的 LV 和 LE 表示该芯片为低电压产品(通常为 3.3V 电压供电);89S52 中的 S 表示该芯片含有可串行下载功能的 Flash 存储器,即具有 ISP 可在线编程功能。
    • 5:固定不变。
    • 2:表示该芯片内部程序存储空间的大小,1 为 4KB,2 为 8KB,3 为 12KB,即该数乘上 4KB 就是该芯片内部的程序存储空间大小。程序空间大小决定了一个芯片所能装入执行代码的多少。我们的芯片是 8KB。
    • RC:STC 单片机内部 RAM(随机读写存储器)为 512B。另,RD+ 表示内部 RAM 为 1280 B。
    • 40:表示芯片外部晶振最高可接入 40MHz。对于 AT 单片机,其值一般为 24,表示其外部晶振最高为 24MHz。
    • C:产品级别,代表商业级,表示芯片使用温度范围,为 0°C~+70°C。
    • PDIP—产品封装型号,表示双列直插式。

    2523:表示本批芯片生产日期为 2025 年第 23 周。

    HBME70.X90C 不详(有关资料显示,此标号表示芯片制造工艺或处理工艺)。

    这样我们就有了一颗 8051 内核,含有 Flash E2PROM 存储器,CMOS,8KB 内存,最高晶振为 40KHZ,生产于 2025 年第 23 周的芯片,使用温度在 0~70 摄氏度之间。

    我们可以看到我们的芯片是 DIP 双列直插式封装的,其他的还有 PLCC(带引线的塑料芯片封装),QFP(塑料方型扁平式封装),PGA(插针网络阵列封装),BGA(球栅阵列封装等),此处按下不表。

    引脚

    我们的单片机是 40 脚 DIP 封装的,也有 20,28,32,44 等。我们以我们的实际图来认识引脚。

    • 首先识别到半圆形的凹槽,这个是我们引脚的起点。通过逆时针的方式,我们的标号如下:

    2|690x278

    我们将引脚可以分成 3 类:

    1. 电源与时钟引脚。VCC、GND、XTAL1、XTAL2

    2. 变成控制引脚。RST、PSEN\overline{\text{PSEN}}、ALE/PROG\overline{\text{PROG}}EA\overline{\text{EA}}/VPP。

    3. I/O 口引脚。例如 P0~P3,四组八位 I/O 口。

    详细了解

    • VccV_{cc}(40 脚)、GNDGND(20 脚)单片机电源引脚,不同型号单片机接入对应电压电源,常压 5V,低压 3.3V。我们的芯片是 5V——3.3V。

    • XTAL1(19 脚),XTAL2(18 脚)外接时钟引脚前者是输入端,后者是输出端

    • 8051 时钟有两种方式:(1)片内时钟振荡。在两脚外接石英晶体与 10~30pF 的振荡电容。(2)外部时钟:输入端接地,输出端接入外部时钟。

    • RST(9 脚)复位引脚

    国庆后给一个 TODO List:

    学习《信号与系统》
    学习《数字信号处理》
    复习《线性代数》
    学习《数学物理方法》
    学习简单的嵌入式编程(51 单片机,STM)

    希望能够在新的一年到来前实现补齐这些基础知识。除此之外,未来想做的 todo list 还有很多很多,给一个长长的,很难完成的 todo-list:

    Rustlings:想要学习新的语言
    gopl: 学习 golang
    15445: 学会 Rust 后尝试重构这个实验
    6.824: 用 golang 写一下分布式系统
    cs144: 写一个 TCP/IP

    先这样吧,感觉缺失的知识很多。。。也不知道自己的毅力能否支持自己完成... 感觉自从过了这个暑假,失去了很多心气,变得很累,还有一种颓丧感。

    希望能够靠不断更新自己的 blog 和自己的楼来进步吧。

    路漫漫其修远兮,吾将上下而求索。加油吧。

    除此之外,Leetcode 我也会坚持继续,希望能够成功达成 365 挑战。

    Leetcode

    截屏 2025-09-25 22.25.54|690x330

    Post #268 ❤️ 7 likes
  • 嗯,打算看完 51 后有时间就去玩 STM

    Post #267 ❤️ 1 like
  • 打算开一些新的坑,随便玩一玩了
    板子刚刚到货,想学习一下 51 单片机
    目前参考的教材是这本:

    截屏 2025-09-25 13.07.11|357x500

    6f05a90c2f93525666b1324623dd8336|624x500

    争取在明年春天前不把这个板子烧坏 🤣

    Post #265 ❤️ 4 likes
  • 当时我面的时候是手撕无锁 mpmc,但是前一天刚好看过 cppcon 所以撕出来了。。。
    现在忘光光啦

    Post #41 ❤️ 2 likes
  • 我也在报复性摆烂,打算国庆后再做事
    这个暑假又实习又在实验室打工累的半死,该放一下小假了,要对自己好一些,后面的路还很长 :smiling_face_with_3_hearts_people_hugging:

    Post #293 ❤️ 4 likes
  • 复健陪一张
    Screenshot_20250922_214241_com.codoon.gps|226x500

    Post #188 ❤️ 5 likes
  • 就是应该这样,感谢树莓老师的负责
    👍️

    Post #192 ❤️ 4 likes
  • 我有一个朋友考过,一散步就跟我这么说 🤣

    Post #6 ❤️ 1 like
  • 这两句凝聚了日语考生们的怒火,不知道也罢
    :cherry_blossom_cat:

    Post #4 ❤️ 2 likes
  • 天気がいいから、散歩しましょう!
    散歩にまび

    Post #2 ❤️ 2 likes
  • 要自信,一定有老师愿意要你科研的

    Post #342 ❤️ 2 likes
  • 第一次感觉有点累了,可能之前没有太拼命吧,谢谢大家的关心。让我自己休息一会儿吧。

    Post #264 ❤️ 4 likes
  • 截屏 2025-09-17 21.41.46|690x248

    Post #262 ❤️ 2 likes
  • 本校工程硕博看起来像是把我默拒了。
    南京大学没有消息。估计也是默拒了。
    原来边缘人 + 三无选手(无项目,无论文,无竞赛)就是死局呀。

    那算了,名额浪费就浪费吧,收拾一下明天重新准备考研吧。向下保是没有意义的,读博=赌博,アイラちゃん断然没有那个能力去。
    签署了承诺书,直到 2026 年 10 月 30 日前都不能参与就业,只有一条路走了。

    虽然很难受但是也不能裹足不前呀。我永远都会记得上海交通大学,感谢这所大学给我三年带来的所有痛苦。

    Post #257 ❤️ 7 likes
  • 西瓜书感觉太理论了,入手会不会困难啊
    可以用这个入手:https://cs229.stanford.edu/

    当时アイラちゃん是用这个学的,学校教的水平有些不太好恭维

    https://www.bilibili.com/video/BV1b4anzMEUv/?spm_id_from=333.337.search-card.all.click&vd_source=043865b6fb4a1d372af1f894ecfa8d36

    Post #332 ❤️ 1 like
  • 国庆 アイラちゃん 打算回家看看爸爸妈妈
    以后能回家的时间会越来越少的,想多陪陪他们

    Post #652 ❤️ 6 likes