Jiahong 的个人博客

凡事预则立,不预则废


  • Home

  • Tags

  • Archives

  • Navigation

  • Search

General——各种镜像源的管理

各种镜像源管理


pip 镜像源

  • 一般来说pip默认使用的源可能会比较慢,此时需要修改成国内的源

查看源

  • 查看当前 pip 源
    1
    pip config list

修改方法:

临时修改
  • 在命令后面添加如下参数即可将安装源换成阿里云

    1
    -i http://mirrors.aliyun.com/pypi/simple --trusted-host mirrors.aliyun.com
  • 只在当前命令中修改,以后想要使用时需要继续添加参数

  • 特别说明: 在阿里云的服务器上使用这个指令时效果非常明显

永久修改
  • 修改文件~/.pip/pip.conf内容, 如果没有该文件则新建一个

    1
    2
    3
    4
    5
    vim ~/.pip/pip.conf
    # write the following contents and save
    # the example is USTC
    [global]
    index-url = https://pypi.mirrors.ustc.edu.cn/simple/
  • 这个命令修改当前用户的默认pip命令镜像

镜像列表

  • 各种镜像列表:

    1
    2
    3
    4
    5
    6
    7
    官方:https://pypi.org/simple
    清华:https://pypi.tuna.tsinghua.edu.cn/simple
    阿里云:http://mirrors.aliyun.com/pypi/simple/
    中国科技大学 https://pypi.mirrors.ustc.edu.cn/simple/
    华中科技大学:http://pypi.hustunique.com/
    山东理工大学:http://pypi.sdutlinux.org/
    豆瓣:http://pypi.douban.com/simple/
  • 其中清华的比较常用


Ubuntu 源

  • Linux 一般默认使用自己系统的源,比如 Ubuntu 使用的就是自己的 Ubuntu 官网源

修改方法

  • 修改文件/etc/apt/source.list
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    sudo /vim/apt/source.list

    # write new image source
    # the example of aliyun
    deb http://mirrors.aliyun.com/ubuntu/ bionic main restricted universe multiverse
    deb http://mirrors.aliyun.com/ubuntu/ bionic-security main restricted universe multiverse
    deb http://mirrors.aliyun.com/ubuntu/ bionic-updates main restricted universe multiverse
    deb http://mirrors.aliyun.com/ubuntu/ bionic-proposed main restricted universe multiverse
    deb http://mirrors.aliyun.com/ubuntu/ bionic-backports main restricted universe multiverse
    deb-src http://mirrors.aliyun.com/ubuntu/ bionic main restricted universe multiverse
    deb-src http://mirrors.aliyun.com/ubuntu/ bionic-security main restricted universe multiverse
    deb-src http://mirrors.aliyun.com/ubuntu/ bionic-updates main restricted universe multiverse
    deb-src http://mirrors.aliyun.com/ubuntu/ bionic-proposed main restricted universe multiverse
    deb-src http://mirrors.aliyun.com/ubuntu/ bionic-backports main restricted universe multiverse

    # update and upgrade
    sudo apt-get update
    sudo apt-get upgrade

镜像源列表

中科大源
  • 中科大源列表
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    deb https://mirrors.ustc.edu.cn/ubuntu/ bionic main restricted universe multiverse
    deb-src https://mirrors.ustc.edu.cn/ubuntu/ bionic main restricted universe multiverse
    deb https://mirrors.ustc.edu.cn/ubuntu/ bionic-updates main restricted universe multiverse
    deb-src https://mirrors.ustc.edu.cn/ubuntu/ bionic-updates main restricted universe multiverse
    deb https://mirrors.ustc.edu.cn/ubuntu/ bionic-backports main restricted universe multiverse
    deb-src https://mirrors.ustc.edu.cn/ubuntu/ bionic-backports main restricted universe multiverse
    deb https://mirrors.ustc.edu.cn/ubuntu/ bionic-security main restricted universe multiverse
    deb-src https://mirrors.ustc.edu.cn/ubuntu/ bionic-security main restricted universe multiverse
    deb https://mirrors.ustc.edu.cn/ubuntu/ bionic-proposed main restricted universe multiverse
    deb-src https://mirrors.ustc.edu.cn/ubuntu/ bionic-proposed main restricted universe multiverse
163 源
  • 163 源列表
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    deb http://mirrors.163.com/ubuntu/ bionic main restricted universe multiverse
    deb http://mirrors.163.com/ubuntu/ bionic-security main restricted universe multiverse
    deb http://mirrors.163.com/ubuntu/ bionic-updates main restricted universe multiverse
    deb http://mirrors.163.com/ubuntu/ bionic-proposed main restricted universe multiverse
    deb http://mirrors.163.com/ubuntu/ bionic-backports main restricted universe multiverse
    deb-src http://mirrors.163.com/ubuntu/ bionic main restricted universe multiverse
    deb-src http://mirrors.163.com/ubuntu/ bionic-security main restricted universe multiverse
    deb-src http://mirrors.163.com/ubuntu/ bionic-updates main restricted universe multiverse
    deb-src http://mirrors.163.com/ubuntu/ bionic-proposed main restricted universe multiverse
    deb-src http://mirrors.163.com/ubuntu/ bionic-backports main restricted universe multiverse
清华源
  • 清华源列表
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic main restricted universe multiverse
    deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic main restricted universe multiverse
    deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic-updates main restricted universe multiverse
    deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic-updates main restricted universe multiverse
    deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic-backports main restricted universe multiverse
    deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic-backports main restricted universe multiverse
    deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic-security main restricted universe multiverse
    deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic-security main restricted universe multiverse
    deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic-proposed main restricted universe multiverse
    deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic-proposed main restricted universe multiverse

Centos 源

修改方法

  • 修改文件/etc/yum.repos.d/CentOS-Base.repo
    1
    2
    3
    4
    5
    6
    7
    8
    9
    # backup
    sudo mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo_bak
    # create new config file
    # the example of aliyun, the config file could be downloaded from remote directly
    sudo wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo
    # update chche
    sudo yum makechche
    # update
    sudo -y update

镜像源列表

网易源
  • Centos6: http://mirrors.163.com/.help/CentOS6-Base-163.repo
  • Centos7: http://mirrors.163.com/.help/CentOS7-Base-163.repo
阿里源
  • Centos6: http://mirrors.aliyun.com/repo/Centos-6.repo
  • Centos7: http://mirrors.aliyun.com/repo/Centos-7.repo

Mac brew 源

  • 参考链接:(Mac 下 brew 切换为国内源)[https://cloud.tencent.com/developer/article/1614039]

conda 源

  • 查看 conda 源:

    1
    conda config --show channels
  • 删除 conda 源:

    1
    conda config --remove channels [target_url]
  • 清空 conda 源:

    1
    conda config --remove-key channels
    • 这个命令不会删除 defaults 源,如果要删除该源,需要手动删除
  • 添加 conda 源:

    1
    conda config --add channels [new_channel_url]
  • 安装时临时指定镜像

    1
    conda install -c [channel_url] [package_name]

conda 源的分类

  • Conda 源的路径后缀可看出其用途:
    • /cloud/pytorch/ :提供PyTorch深度学习框架及其依赖库
    • /cloud/menpo/ :提供计算机视觉相关工具(如dlib、OpenCV)
    • /cloud/conda-forge/ :社区维护的开源包(覆盖科学计算、数据分析等领域)
    • /pkgs/free/ :Anaconda早期免费版仓库(现多合并至main)
    • /pkgs/main/ :Anaconda官方核心包(Python、NumPy、SciPy等基础库)

以清华源为例

  • 示例添加清华源:

    1
    2
    3
    conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/
    conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main/
    conda config --set show_channel_urls yes
    • conda config --set show_channel_urls yes 可让后续的安装都打印 conda 源信息
  • 一条指令恢复默认源(清空所有源):

    1
    conda config --remove-key channels

Git——GitHub-Pages的CNAME设置


CNAME是什么

  • CNAME是DNS解析中的一种别名记录,允许同一个网站有两个不同的记录解析过去
  • CNAME在GitHub Pages中体现为可以将xxx.github.io的域名添加别名为自定义的域名(这个域名通常是自己买的,在域名那里也要配置解析到当前xxx.github.io)

GitHub Pages中使用CNAME时遇到的问题

  • 我们可以在GitHub账户中设置CNAME
  • 重新提交commit并推送数据到GitHub上后,之前设置的CNAME会被删除,需要重新设置,也就是说每次push操作后都需要重新设置
    • 这个问题在hexo d后也会出现

解决方案

  • 在当前项目的GitHub Pages对应的分支下面新建一个文件,命名为CNAME
  • 打开文件并新建一行为目标网站的域名(如果是中文域名则需要编码转换)
  • 在Hexo中,我们可以在source文件夹下面新建该文件
    • hexo generate时会将source文件夹下面的文件都拷贝到public文件夹下面

Git——使用总结

  • 参考链接:
    • 值得参考的博客:https://segmentfault.com/a/1190000008617626
    • 更详细解释一般直接查看 git (command) -h 即可

总体概况总结

  • 简单结构图:
  • 全局结构图:

常见的分支管理

  • 查看本地分支

    1
    git branch
  • 查看所有分支(包括远程)

    1
    git branch -a
  • 删除本地分支

    1
    2
    3
    4
    # 安全删除(safe delete),Git 会检查该分支是否已经完全合并到当前分支(或者 HEAD 所指的分支);如果分支上还有未合并的提交,Git 会阻止删除
    git branch -d branch_name
    # 强制删除(force delete);等价于:'git branch --delete --force <branch>',不管分支是否已合并,都会直接删除
    git branch -D branch_name
  • 删除远程分支

    1
    git push origin :branch_name
    • 将空的东西推送到远程的branch_name分支,也就是删除远程分支
  • 新建本地分支

    1
    2
    3
    4
    5
    6
    # 将远程分支拉取到本地的 Repository 中,但不修改本地工作目录,如果本地分支不存在,则新建分支
    git fetch origin master:branch_name
    # 从当前分支 fork 一个分支出来
    git checkout -b branch_name
    # 若 branch_name 分支在本地工作目录不存在,但是 origin 上存在,则本地工作目录上会创建 branch_name
    git checkout branch_name
  • 新建远程分支

    1
    git push origin master:branch_name
    • 将本地分支推送到远程,如果branch_name不存在,则新建分支

冲突管理

远程分支和本地分支有不同的 commit

  • git pull和git push均产生reject异常

  • 使用下面语句拉取远程分支到本地old分支并合并

    1
    2
    git fetch origin master:old
    git merge old
  • 上面两句等价于

    1
    git pull origin master:old
    • 注意:不建议使用 pull 拉取其他分支,容易产生操作失误
  • 如果能够快速合并,也就是相同文件没有同时被不同提交修改:

    • 上面的语句将弹出一个合并窗口提示输入合并这个操作(提交)的 Comment,按照提示提交保存即可
    • 保存后自动生成一个以刚才的 Comment 命名的提交
  • 不能快速合并时

    • 上面的语句会提示我们哪些文件有合并冲突需要解决的
    • 我们需要根据提示找到并修改文件中冲突
    • 然后重新提交(像正常提交代码一样即可)
      1
      2
      git add .
      git commit -m " "
  • 合并完成后删除多余分支

    1
    git branch -d old

附录:fetch 和 pull 两者比较:

1
2
git pull == git fetch + git merge
git pull origin master:old == git fetch origin master:old + git merge old

git diff

  • git diff A B
    • 基于 A 查看 B 有何变化
    • 显示时会自动识别为基于 a/A 看 b/B 有何变化

文件恢复

恢复本地缓冲区文件到disk

  • 恢复某个文件:git checkout -- xx/xx.py
  • 恢复某个文件夹下所有文件:git checkout -- xx/*
  • 指定恢复某个分支到指定文件:git checkout master -- xx/xx.py

恢复HEAD到缓冲区

  • 仅恢复到缓冲区:git reset or git reset HEAD
  • 不仅恢复到缓冲区,同时恢复到disk: git reset HEAD --hard

从远程拉取一个新分支

  • 从远程拉取分支到本地:git fetch origin xxx
    • 此时使用 git branch -a 可以看到远程分支引用在本地
    • git fetch origin xxx 后,FETCH_HEAD 会指向远程 xxx 分支
      • FETCH_HEAD 是个临时的引用,可以对该分支做任意想做的操作,比如此时可用 git merge FETCH_HEAD 来合并新拉取的分支
  • 创建本地同名新分支:git checkout xxx
    • 必须同名

git clone 进阶

  • git clone ssh:xxx 默认拉取master分支
  • git clone -b [branch] ssh:xxx 拉取 [branch] 分支

git clone 仅拉取某个分支

  • 仅拉取某个分支,用于大型项目

    1
    git clone --single-branch --branch [branch] ssh:xxx
  • 如果想进一步降低下载量(不关心历史记录),可以加上 --depth 1 参数

  • 注意:如果使用下面的命令(即不添加 --single-branch 参数),会拉取所有分支内容 ,但是本地仅检出目标分支

    1
    2
    3
    git clone --branch [branch] ssh:xxx
    # 等价简写形式
    git clone -b [branch] ssh:xx

删除本地跟踪远程分支

  • git branch -d --remotes origin/xxx
    • 测试发现:git 1.7.1会报错,git 2.24.3没问题

git pull 和 git push 默认分支设定

  • git branch --set-upstream-to=origin/xxx xxx
    • 经测试:git 1.7.1会报错,git 2.24.3没问题(但git pull不会生效)

添加文件到.gitignore

  • 如果文件已经被添加到Git仓库中(常常出现在一些不规范的项目中),则可以考虑使用以下步骤解决:
    • 假设Git仓库中已经把.DS_Store添加到Git仓库中
    • 首先拉取项目并在.gitignore中添加*.DS_Store
    • 执行git rm --cached *.DS_Store, 从缓冲区中删除所有*.DS_Store文件,但保留本地文件
    • 执行git add .并重新提交

在目录内部忽略自身

  • 可以不修改外部的 .gitignore 文件(即不修改项目根目录下的 .gitignore 文件),通过在某个目录下增加 .gitignore 文件来定义子目录下的忽略规则

  • 用法示例:./venv/.gitignore

    1
    *
    • 表示忽略该 ./venv/ 目录下的所有文件
  • 这种方法很有益于管理本地多余的文件夹,因为不需要修改到 项目本身的 .gitignore 文件


Git 标签的使用

  • Git 标签(tag)是用于标记仓库中特定提交节点的引用,常用于版本发布(如 v1.0.0)、重要里程碑等场景,方便后续快速定位和回溯

标签类型

  • 轻量标签(lightweight)
    • 仅作为某个提交的“指针”,不包含额外信息(如创建者、时间、注释),本质是一个 commit_id 的引用
    • 适合本地临时标记
  • 带注释标签(annotated)
    • 完整的标签对象,包含标签名、创建者、时间戳、详细注释等信息,会被 Git 永久存储在仓库中
    • 适合正式版本发布(推荐使用)

创建标签

  • 创建带注释标签(推荐)

    1
    2
    git tag -a <标签名> -m "<标签注释>"
    # 示例:git tag -a v1.0.0 -m "正式发布 v1.0.0 版本"
    • -a:指定创建带注释标签
    • -m:直接添加注释(若省略,会打开编辑器输入)
  • 给历史提交打标签:默认标签是打在当前最新提交上,若要给历史提交打标签,需指定 commit_id (可通过 git log 查看):

    1
    2
    git tag -a <标签名> <commit_id> -m "<注释>"
    # 示例: git tag -a v0.9.0 a1b2c3d -m "修复 bug 后的预发布版本"
  • 创建轻量标签:无需 -a 和 -m,直接指定标签名:

    1
    2
    3
    git tag <标签名>  # 默认为当前提交
    # 或指定历史提交
    git tag <标签名> <commit_id>

查看标签

  • 列出所有标签(仅列出标签名)

    1
    git tag
  • 按字母顺序排列(非创建时间,仅列出标签名),可加筛选(如查看 v1 开头的标签)

    1
    git tag -l "v1.*"  # -l 表示筛选(list)
  • 查看标签详情:查看带注释标签的完整信息(包括创建者、时间、注释、对应提交等)

    1
    2
    git show <标签名>
    # 示例:git show v1.0.0 # 查看 `v1.0.0` 的详情
  • 列出远程标签(包含标签 id 和 commit_id 等)

    1
    git ls-remote --tags origin
    • 列出的结果会同时包含标签自己的实体 id(如 v1.1.0) 和 对应的 commit_id (如 v1.1.0^{})
  • 列出本地标签(包含标签 id)

    1
    git show-ref --tags

推送和拉取远程仓库标签

  • 创建的标签默认只保存在本地,需手动推送到远程仓库

  • 推送单个标签

    1
    2
    git push origin <标签名>
    # 示例:git push origin v1.0.0
  • 推送所有本地未推送的标签

    1
    git push origin --tags
  • 拉取远程仓库所有标签

    1
    git fetch origin --tags
  • 拉取远程仓库单个标签到指定标签

    1
    2
    3
    git fetch origin refs/tags/v1.2.0:refs/tags/v1.2.0
    # 等价命令
    git fetch origin v1.2.0:refs/tags/v1.2.0 # 在不会引起误会的情况下,前面的前缀 refs/tags/ 可以删除,但后面的不可以
    • 本地会新增 v1.2.0 标签,指向与远程该标签相同的 commit
    • 注:refs/tags/v1.2.0 中的 refs/tags/ 是标签的前缀完整命名,是必要的,否则会被当做 branch(实际上 branch 的完整命名是 refs/heads/xx)

删除标签

  • 删除本地标签

    1
    git tag -d <标签名>
  • 删除远程仓库的标签:需先删除本地标签,再推送删除操作到远程:

    1
    2
    git tag -d <标签名>  # 先删本地
    git push origin --delete <标签名> # 再删远程

检出标签(查看标签对应的代码)

  • 标签是静态的(不可修改),检出标签时会进入“分离头指针”状态(detached HEAD),此时修改不会影响任何分支,适合临时查看历史版本:

    1
    git checkout <标签名>
  • 若要基于标签修改代码,需创建新分支:

    1
    git checkout -b <新分支名> <标签名>

标签管理最佳实践

  • 命名时,建议使用语义化版本(如 v<主版本>.<次版本>.<修订号>,例 v2.1.3),清晰区分版本迭代
  • 优先使用带注释标签,包含完整信息,便于协作和追溯
  • 推送标签前确认,标签一旦推送到远程,尽量避免删除(尤其是已发布的版本标签),如需删除需同步团队
  • 发布版本时,在 release 或 main 分支打标签,确保标签对应稳定代码

Git 配置相关

  • 用户配置查看

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    # 查看所有配置(含用户信息)
    git config --list

    # 查看「当前生效」的用户名/邮箱
    git config user.name
    git config user.email

    # 查看「当前仓库」的用户配置(仅对当前项目生效)
    git config --local user.name
    git config --local user.email

    # 查看全局「用户」配置(对所有仓库生效,默认常用)
    git config --global user.name
    git config --global user.email

    # 查看系统级用户配置(需管理员权限,Windows 可能需要以管理员打开终端,这个命令很少用)
    git config --system user.name
    git config --system user.email
  • 用户配置设置

    1
    2
    3
    4
    5
    6
    7
    # 全局配置(所有仓库通用,推荐)
    git config --global user.name "用户名"
    git config --global user.email "邮箱"

    # 仅当前仓库配置(覆盖全局,适合单独项目需求)
    git config --local user.name "用户名"
    git config --local user.email "邮箱"

ACM——编程中应该注意的一些关键问题

持续更新


位运算

用移位代替乘除法

  • 移位的效率远高于乘除法
  • 除以2时一般直接右移一位即可
  • 乘以2时一般直接左移一位即可

用位运算判断奇偶性

  • 将整数与1做与运算,值为1表示为奇数,为0表示为偶数

判断二进制数中1的个数

方法一: n=(n-1)&n
  • n = (n-1) & n 可以将二进制数n中的最后一位1变成0
  • 不停执行该语句,直到n为0,执行次数即二进制数n中1的数量
方法二: result=n&flag
  • flag初始化为与n相同的类型(二进制位数一致)
  • flag从1,2,4….,为2的次方数,即初始化为1,每次向左移动一位,直到flag为0为止(此时说明flag移位溢出了,n的每一位都进行过比较了)
  • 累计result为1的次数就是二进制数n中1的数量
错误方法: result=n&1
  • 这种方法不设置flag变量,每次将n左移一位,直到n为0
  • 当n为负数时会引发无线循环,所以此方法是错的

小数的精度

比较两个小数是否相等

  • 无论a,b类型是否相等(float, double),都不能直接使用==符号直接比较
  • 定义以下函数实现比较即可|a-b| < \(\epsilon\) (\(\epsilon\) 是一个误差允许的极小值)
    1
    2
    3
    4
    5
    bool Equal(double num1, double, num2){
    if(num1-num2 < 0.0000001 && num1-num2 > -0.0000001)
    return true;
    return flase;
    }

为什么存在精度问题

  • 因为十进制的小数存到内存时是二进制,此时有很多无线循环,但是二进制位数有限,所以有误差

  • 一般来说,double类型足以满足我们日常计算的精度,在无需很高精度时使用float可以减少内存占用

  • 实例:

    1
    2
    3
    4
    5
    6
    7
    8
    double a = 0.9;
    float b = 0.9;
    if(a == (double)b)
    printf("a==b is true")
    else
    printf("a==b is flase")
    # output:
    a==b is false
    • 对于小数0.9,表示为二进制后小数部分是无线循环小数,在float类型时与double类型时由于保留的小数位不同,所以值不同

最大最小整数

最小负数

1
2
3
4
# int
int INT_MIN = 0x80000000; //32位
# long
long LONG_MIN = 0x8000000000000000; //64位

最大正数

1
2
3
4
# int
int INT_MAX = 0x7FFFFFFF; //32位
# long
long LONG_MAX = 0x7FFFFFFFFFFFFFFF; //64位

第一要务:输入检查

  • 无论何时,函数实现的第一步都应该是检查输入是否合法

常见的检查

  • 指针:是否为空
  • 整数:是否满足条件(大于0,小于0等)
  • 多个整数之间的关系:是否满足大小关系等

访问数组时必须首先确认

  • 数组已初始化
  • 访问的Index没有超过数组

关于折半搜索和查找

  • 一般来说low从0开始,high从数组长度开始(注意,有时候high如果从len-1开始则可能造成无法访问到最后一个位置?)
    • 相关题目:LeetCode 35题 Search Insert Position

mid定义为(low + high)/2

  • 如果mid的定义如下

    1
    mid = (low + high) / 2
  • 那么每次查找结束时更新时应该作如下更新

    1
    2
    low = mid + 1
    high = mid
  • 或者

    1
    2
    low = mid + 1
    high = mid - 1
  • 如果此时使用下面的语句作为更新

    1
    low = mid
    • low == high - 1有low == mid,上面的式子将造成死循环

mid定义为(low + high + 1)/2

  • 如果mid的定义如下

    1
    mid = (low + high + 1) / 2
  • 那么每次查找结束时更新时应该作如下更新

    1
    2
    low = mid
    high = mid - 1
  • 或者

    1
    2
    low = mid + 1
    high = mid - 1
  • 如果使用下面的语句作为更新

    1
    high = mid
    • low == high - 1有high == mid,上面的式子将造成死循环
  • 二分查找相等的数时往往可以使用

    1
    2
    low = mid + 1
    high = mid - 1
  • 但是查找的是当前数组不存在的节点的存放位置时要注意查找时

    1
    2
    low = mid + 1
    high = mid
    • 这样确保i和j退出时相等
    • 相关题目:LeetCode 35 Search Insert Position

相关题目

  • 详细情况参考LeetCode 34题 Find First and Last Position of Element in Sorted Array

深度优先遍历和回溯法的异同

  • 深度优先方法(DFS, Depth First Search)使用栈实现,广度优先方法(BFS, Breadth First Search)使用队列实现

  • 一般来说在树中遍历时才有深度优先搜索这种说法,不然比如数组的全部组合遍历都叫做回溯法,而不是DFS

  • 对于树而言,回溯法也是遵循深度优先遍历原则实现的

  • 回溯法可以访问已经访问过的节点,而深度优先一般来说不会再访问已经访问过的节点

  • DFS是把所有可能的情况考虑到就行了,但回溯法可能会重视访问节点的顺序[LeetCode 79 Word Search]

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    def solution(nums):
    path, result = [], []
    * 如果nums含有重复元素,需要重新排序
    def backtrace(index):
    if 满足存储条件:
    result.append(path[:])
    if 不需要继续迭代:
    return
    * 迭代访问内部节点(不同情况访问方式不同,详情看下面)
    * 一种简单的可能是
    for i in range(*视具体情况变化):
    * 如果nums含有重复元素,需要判断是否跳过(如果nums当前节点与上一个节点重复,则跳过)
    path.append(nums[i])
    * 不同情况不同回溯方式
    * 当path里面不能重复访问同一元素时
    * backtrace(i+1)
    * 当path里面能重复访问同一元素时
    * backtrace(i)
    path.pop()
    backtrace(0)
    return result
  • 特殊情况,如果path不能访问同一元素而且不同顺序相同元素算是不同路径的时候,我们可能必须使用rest对象在迭代中记录当前未被访问的对象

    • 此时由于不同顺序相同元素算是不同路径,所以我们不能用每次只能向前访问的方法来实现去除重复(及[index:], 这种每次只能向前访问的方法会将先[a, b], [b, a]视为重复而去除,只保留[a, b]当(a < b时))

关于递归函数内部的节点访问方式

其实在使用回溯法访问一个数组时,可以考虑这个我们在访问一棵树,从空节点开始,然后每次迭代所有符合的子节点

| 路径是否可以访问同一节点多次 | 顺序不同节点相同是否算不同路径 | 原始数组中是否包含重复元素 | 预处理 |每一层迭代代码 |
| :——: | :——: | :——: | :——: |
| 是 | 是 | 是 | data.sort() | for i in [:]:
  if与前一个相等:
    continue
  visit(i)
  backtrace()
最后移除重复路径|
| 是 | 是 | 否 | - | for i in [:]:
  visit(i)
  backtrace() [待更新]|
| 是 | 否 | 是 | data.sort() | for i in [index:]:
  if与前一个相等:
    continue
  visit(i)
  backtrace(i)
最后移除重复路径|
| 是 | 否 | 否 | - | for i in [index:]:
  visit(i)
  backtrace(i)|
| 否 | 是 | 是 | data.sort()
rest存储剩余元素| for i in rest:
  if与前一个相等:
    continue
  visit(i)
  backtrace(rest-i)|
| 否 | 是 | 否 | rest存储剩余元素 | for i in rest:
  visit(i)
  backtrace(rest-i) |
| 否 | 否 | 是 | data.sort() | for i in [index:]:
  if与前一个相等:
    continue
  visit(i)
  backtrace(i+1) |
| 否 | 否 | 否 | - | for i in [index:]:
  visit(i)
  backtrace(i+1)|

相关理解
  • 当节点相同顺序不同是相同路径时,使用每次迭代只向后而不向前访问的方式可以避免重复(因为先a后b,与先b后a重复)

    • 路径可含有重复节点时,向后迭代从当前节点开始,包括当前节点backtrace(i)
    • 路径不可包含重复节点时,向后迭代从当前节点的下一个开始,不包括当前节点backtrace(i+1)
  • 当路径不可包含重复节点时,还可使用rest存储尚未访问的节点或每次迭代时只向后即可解决问题

  • 当数组中包含重复节点时,可以现将其排序,然后在迭代时跳过与前一个节点相同的节点

  • 一个万能的写法是:

    • 每次迭代访问所有节点
    • 将所有符合条件的路径加入结果
    • 最后将重复路径删除即可
  • 1,3这两种情况实际中很少出现,我们不能提前用某种算法确保加入的数据不重复,这时需要我们最终从result中去除重复的(或者每次都查看result中是否有与当前路径相同的路径)

    • 第1,3两种情况中,即使我们保证访问顺序完全相同的路径不会重复出现,但是由于数组中可能有重复值,且每个元素可能被访问多次
      • 比如[2,1,1]中,容易造成同一个元素a[1]被访问两次的到的[1,1]路径与两个元素a[1],a[2]分别被访问一次得到的[1,1]路径结果完全相同,此时我们只能通过最终手段保证返回结果中路径的唯一性
    • 保证路径的唯一性在Python中可以使用将路径转换为tuple(path)的方式实现路径的比较,且可以放入set中
    • 第1, 3两种情况的不同之处在于第1种情况中不同顺序的路径是不同的,所以无需对路径进行排序,第3种情况中不同顺序相同结点的路径也是相同结点,则需要先对路径进行排序,再转换为tuple才行
  • 第4种情况对应LeetCode 39 Combination Sum

  • 第7种情况对应LeetCode 40 Combination Sum II

  • 第8种情况对应LeetCode 216 Combination Sum III

  • 第5种情况对应LeetCode 47 Permutations II


对列表中数字列表的排序

1
List[List[number]]
  • 找出所有列表的所有数中最大的数M

  • 定义一个函数,用一个M+1进制的数对每个内层列表进行映射表示

    • 系数:

      • 需要递增排序的位前面系数为1
      • 需要递减排序的位前面系数为-1
      • 无需排序的位前面系数为0
    • 示例:

      1
      2
      def key(x):
      return x[0]*(M+1)**3 - x[1]*(M+1)**2 - x[2]*(M+1) + x[3]
      • 对第一和第四位按照递增排序,第二和第三位按照递减排序
  • 映射函数定义后可以使用sort函数排序

    1
    l.sort(key=key)
  • 也可以使用lambda函数定义

    1
    l.sort(key=lambda x: x[0]*(M+1)**3 - x[1]*(M+1)**2 - x[2]*(M+1) + x[3])

使用 memo 存储中间结果

memo可理解为备忘录,但是和传统的备忘录设计模式有一定区别,后者的目标是恢复对象之前的某个状态,前者的目标更应该理解为动态规划的思想dp存储计算中间值

  • 对某些可能会多次被求解的子空间(子数组,子集等), 且子空间能够独立的情况
    • 这种题目对应一般可以使用DFS求解,但是由于子空间能够独立,使用DFS会多次求解子空间,从而产生超时的情况
    • 子空间能够独立的就应该使用memo
  • 如果能够唯一标识(能作为字典的Key值)这些空间数据,即可使用memo存储
  • memo 为一个已子空间唯一标识为 Key, 子空间的对应求解结果为Value
  • 相关题目: LeetCode 140, Word Break II

在使用特殊字符节省空间

  • 在使用DFS搜索空间中的路径时,往往需要记录访问过的点,不能重复访问
  • 常用的做法是开辟一个新的相同大小的visited数组,记录是否访问过当前结点
  • 一种更好的做法是每次记录当前结点的值, 然后修改为一个特殊字符, DFS 完成后恢复成原始字符, 这种做法无需开辟新的空间

示例:0-1背包问题求解

  • 动态规划典型示例(Python求解示例)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    # 假设:n是物品数,W是背包容量,W是
    # 方案一:时间O(nW),空间O(nW)
    def knapsack01(weights, values, capacity):
    n = len(weights)
    dp = [[0]*(capacity+1) for _ in range(n+1)]
    for i in range(1, n+1):
    for j in range(1, capacity+1):
    if weights[i-1] > j:
    dp[i][j] = dp[i-1][j]
    else:
    dp[i][j] = max(dp[i-1][j], dp[i-1][j-weights[i-1]] + values[i-1])
    return dp[n][capacity]

    # 方案二:时间O(nW),空间O(W)
    def knapsack01_optimized(weights, values, capacity):
    n = len(weights)
    dp = [0]*(capacity+1)
    for i in range(1, n+1):
    for j in range(capacity, weights[i-1]-1, -1): # 注意此时需要逆序遍历,且仅遍历到weights[i-1](包含)为止
    dp[j] = max(dp[j], dp[j-weights[i-1]] + values[i-1])
    return dp[capacity]

    # 方案二(更容易理解的版本):时间O(nW),空间O(W)
    def knapsack01_optimized2(weights, values, capacity):
    n = len(weights)
    dp = [0 for _ in range(capacity+1)]
    for i in range(1, n+1):
    for j in range(capacity, 0, -1): # 注意需要逆序遍历
    if j < weights[i-1]:
    pass # dp[i][j] = dp[i-1][j] => dp[j] = dp[j] => 可以直接pass
    else:
    dp[j] = max(dp[j], dp[j-weights[i-1]] + values[i-1])
    return dp[capacity]

    # 测试用例
    weights = [2, 3, 4, 5]
    values = [3, 4, 5, 6]
    capacity = 8

    max_value = knapsack01(weights, values, capacity)
    print(f"Maximum value: {max_value}")
    max_value = knapsack01_optimized(weights, values, capacity)
    print(f"Maximum value: {max_value}")
    max_value = knapsack01_optimized2(weights, values, capacity)
    print(f"Maximum value: {max_value}")

    # 其他方法说明
    ## 若遇到capacity过大的情况,可考虑使用贪心求解法近似求解:每次加入单位容量价值最大的物品

示例:字符串匹配问题

  • 问题描述:给你一个字符串和一个正则表达式,请你来实现一个支持.和*的正则表达式匹配
    • .匹配任意单个字符
    • *匹配零个或多个前面的那一个元素
    • 要求匹配整个字符串而不是部分字符
  • 动态规划典型示例(Python求解示例)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    # 判断是否匹配单个字符
    def match_char(se, rege):
    return True if rege == '.' else se == rege

    # 时间复杂度O(mn),空间复杂度O(mn);字符串s和正则表达式reg的匹配
    def match_str(s, reg):
    m, n = len(s), len(reg)
    dp = [[True] * (n+1) for _ in range(m+1)]

    # 字符串和正则表达式同时为空时,可以匹配
    dp[0][0] = True

    # 初始化边界:当字符串为空时,正则表达式可能是"x*"的形式
    for j in range(1, n+1):
    if reg[j-1] == '*':
    dp[0][j] = dp[0][j-2] # s=""时,reg可以是"x*"
    else:
    dp[0][j] = False

    # 初始化边界:当正则表达式为空时,字符串只能为空,否则不匹配
    for i in range(1, m+1):
    dp[i][0] = False

    for i in range(1, m+1): # 注意访问从1开始
    for j in range(1, n+1): # 注意访问从1开始
    if reg[j-1] != '*':
    if match_char(s[i - 1], reg[j - 1]):
    # 直接匹配,动态规划看去掉匹配的尾部字符后,两个字符串s[:i-1],reg[:j-1]是否匹配
    dp[i][j] = dp[i-1][j-1]
    else:
    # 不匹配,直接赋值为False,在初始化为False的情况下,这里可以不调用
    dp[i][j] = False
    else: # reg[j-1] == '*'
    if match_char(s[i - 1], reg[j - 2]):
    # 1)dp[i-1][j]:"x*"匹配对应字符,需要保证截止到当前的正则表达式能匹配s[:i-1]的字符
    # 2)dp[i][j-2]:"x*"不匹配对应字符,相当于本次"x*"匹配空字符串"",需要保证正则表达式在"x*"完成当前所有字符的匹配
    # 3)(可选):dp[i-1][j-2]这表示"x*"匹配对应字符且"x*"在这之前不匹配任何字符,这是情况1)的一个子集
    # # 对3)进一步的理解是:因为当reg[j-1] == '*'时,dp[i-1][j] = dp[i-1][j-2]一定成立,故而可以去掉一个部分呢
    dp[i][j] = dp[i-1][j] or dp[i][j-2]
    else:
    # 由于无法匹配,所以字符保留,"x*"不匹配对应字符,正则表达式必须在"x*"完成当前所有字符的匹配
    dp[i][j] = dp[i][j-2]
    return dp[m][n]

    s = "abcdddbc"
    reg = "ab.*b*c"
    print(match_str(s, reg))

NLP——Megatron框架的使用

  • 参考链接:
    • 原始论文:Megatron-LM: Training Multi-Billion Parameter Language Models Using Model Parallelism, arXiv 20200313, NVIDIA
    • 官网主页:Megatron Core User Guide
    • GitHub开源地址:github.com/NVIDIA/Megatron-LM

Megatron 整体说明

  • Megatron 是由 NVIDIA 开发的一个用于训练 LLM 的高性能框架
  • Megatron 专为分布式训练优化,支持模型并行、数据并行和混合精度训练,能够在数千个 GPU 上高效运行
  • Megatron 集成了多种优化技术,包括张量并行、管道并行、激活检查点等,显著提升了超大规模模型的训练效率
  • Megatron 通常与 DeepSpeed 结合使用,形成 Megatron-DeepSpeed 框架,进一步增强训练能力

Megatron 安装

  • Megatron 的安装需要结合 NVIDIA 的环境和依赖,以下是详细的安装步骤:
  • 推荐在 Linux 系统上安装

安装依赖项 PyTorch

  • 根据 CUDA 版本安装对应的 PyTorch:
    1
    2
    3
    4
    5
    # 对于CUDA 11.8
    pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118

    # 对于CUDA 12.1
    pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121

安装 Megatron

  • 下载源码并安装
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    git clone https://github.com/NVIDIA/Megatron-LM.git
    cd Megatron-LM

    pip install -r requirements.txt

    # 安装apex(用于混合精度训练)
    git clone https://github.com/NVIDIA/apex
    cd apex
    pip install -v --disable-pip-version-check --no-cache-dir --global-option="--cpp_ext" --global-option="--cuda_ext" ./
    cd ..

    # 安装其他依赖
    pip install sentencepiece regex datasets

验证安装是否成功

  • 创建一个简单的Python脚本来验证 Megatron 是否正确安装:
    1
    2
    3
    4
    5
    6
    import torch
    from megatron import get_args
    from megatron.model import GPTModel

    # 能导入相关包,说明 Megatron 已成功安装
    print("Megatron安装成功!")

Megatron 数据处理

  • 详情参考:github.com/NVIDIA/Megatron-LM

  • 数据预处理负责将 .jsonl 的文本数据 tokenize 并处理成 Megatron 可以直接读取的数据格式(.bin 和 .idx 类型的文件),减少训练时的数据处理时间

  • 准备 .jsonl 文件,文件格式如下:

    1
    2
    {"text": "Your training text here..."}
    {"text": "Another training sample..."}
  • 数据预处理:

    1
    2
    3
    4
    5
    6
    7
    python tools/preprocess_data.py \
    --input data.jsonl \
    --output-prefix processed_data \
    --tokenizer-type HuggingFaceTokenizer \
    --tokenizer-model /path/to/tokenizer.model \
    --workers 8 \
    --append-eod
    • output-prefix:输出文件的前缀
    • append-eod:是否添加 EOD Token?
    • 注意:还可以根据需要设置 split_sentences 参数,对文档进行拆分成 sentence 再做 tokenize

Megatron 训练开源标准大模型

  • Megatron 支持对一些标准的开源大模型进行训练,比如 GPT2,此时不需要修改代码
  • 这种标准流程包含以下两部分:
    • 需要处理数据为 Megatron 支持的格式
    • 使用命令行启动任务
  • 本文暂不对这部分进行详细讲解

Megatron 训练自定义模型

核心思路

  • 需要做到如下事情
    • 1)先把 Megatron 自带的 GPT/BERT/T5 的「壳」理解透
    • 2)再把自己的网络结构「套」进去
    • 3)最后复用 Megatron 的并行、优化器、数据管道即可
  • 基本思路:
    • 使用 Megatron 训练自定义模型,不需要改 Megatron 核心 ,只实现 3-4 个钩子即可
    • 一些底层的高阶功能,如并行、混合精度、检查点 全部复用官方实现
    • 任何模型(CNN、RWKV、RetNet…)只要包装成 MegatronModule 并返回 loss ,都能用 Megatron 训练

目录结构

  • 目录结构如下,建议自建文件都放到统一的新文件夹 my_model 下
    1
    2
    3
    4
    5
    6
    7
    8
    Megatron-LM/
    ├─ megatron/ # 官方代码不动
    ├─ examples/ # 官方示例
    ├─ my_model/ # 我们自己的
    │ ├─ __init__.py
    │ ├─ model.py # 自定义网络
    │ ├─ layer.py # 自定义层
    │ └─ train.py # 入口脚本

自定义模型的 4 个关键钩子说明

  • Megatron 的训练循环入口是 pretrain(),它通过 4 个可插拔函数 决定「数据长什么样、模型长什么样、前向怎么算、验证看啥指标」:
    钩子名称 作用 示例文件
    model_provider 返回 nn.Module my_model/model.py
    train_valid_test_dataset_provider 返回三个 Dataset my_model/data.py
    forward_step_func 定义一次前向/损失 my_model/train.py
    process_non_loss_data_func TensorBoard 画额外指标(可选) my_model/train.py

模型钩子(model_provider)

  • 新建 my_model/model.py:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    from megatron.model.module import MegatronModule
    from megatron import get_args

    class MyCustomModel(MegatronModule):
    def __init__(self, num_tokentypes=0):
    super().__init__()
    args = get_args()
    self.embed = nn.Embedding(args.padded_vocab_size, args.hidden_size)
    # 这里换成自己的自定义层
    self.backbone = ConvNextBackbone(args.hidden_size, args.num_layers)
    self.lm_head = nn.Linear(args.hidden_size, args.padded_vocab_size)

    def forward(self, input_ids, position_ids, attention_mask, labels=None):
    x = self.embed(input_ids)
    x = self.backbone(x, attention_mask) # [b, s, h]
    logits = self.lm_head(x) # [b, s, V]

    if labels is None:
    return logits
    loss = F.cross_entropy(logits.view(-1, logits.size(-1)), labels.view(-1))

    # Megatron 的 GPTModel 实现也是在 Model.forward 直接返回 loss 的
    return loss

    def model_provider(pre_process=True, post_process=True):
    """给 Megatron 调用的工厂函数,负责返回 MegatronModule 类的模型对象"""
    return MyCustomModel()

数据钩子(train_valid_test_dataset_provider)

  • 把 .jsonl/txt 转成 Megatron 的 IndexedDataset:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    # my_model/data.py
    from megatron.data.dataset_utils import build_train_valid_test_datasets
    from megatron import get_args

    def train_valid_test_dataset_provider(train_val_test_num_samples):
    args = get_args()
    return build_train_valid_test_datasets(
    data_prefix=args.data_path,
    splits_string=args.split,
    train_valid_test_num_samples=train_val_test_num_samples,
    seq_length=args.seq_length,
    masked_lm_prob=0.15 if args.task == 'BERT' else 0.0,
    seed=args.seed,
    skip_warmup=True,
    )

前向钩子(forward_step_func)

  • 定义前向过程(包含 loss 计算,需要返回 loss)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    # my_model/train.py
    from megatron.training import get_model
    from megatron.utils import average_losses_across_data_parallel_group
    from megatron import get_args

    def forward_step(data_iterator, model):
    """一次 micro-batch 的前向"""
    args = get_args()
    tokens = next(data_iterator)['text'].long().cuda()
    labels = tokens[:, 1:].contiguous()
    tokens = tokens[:, :-1]
    position_ids = torch.arange(tokens.size(1), device=tokens.device).unsqueeze(0)
    attention_mask = (tokens != args.pad_token_id).unsqueeze(1).unsqueeze(2)

    # 因为传入 labels 参数时,模型的 forward 已经计算出来 loss 了,这里可以不需要自己写参数
    loss = model(tokens, position_ids, attention_mask, labels)
    reduced = average_losses_across_data_parallel_group([loss])

    # 第一个返回值必须是 loss,第二个返回值可以是任意想要记录的辅助信息
    return loss, {'lm loss': reduced[0]}

将钩子传入 pretrain() 函数

  • 调用 pretrain(),传入前面定义的钩子
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # my_model/train.py
    from megatron.training import pretrain

    if __name__ == '__main__':
    pretrain(
    train_valid_test_dataset_provider, # 用于提供训练、验证和测试数据集的函数或模块
    model_provider, # 用于构建模型的函数,调用它可返回模型实例
    ModelType.encoder_or_decoder, # 或 encoder_decoder
    forward_step, # 定义模型前向传播步骤的函数,包括输入处理、模型计算和损失计算等
    process_non_loss_data_func=None # tensorboard 的 额外指标,process_non_loss_data_func 在这里暂未实现
    )

启动训练

  • 单节点 8 卡示例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    torchrun --nproc_per_node=8 my_model/train.py \
    --tensor-model-parallel-size 2 \
    --pipeline-model-parallel-size 2 \
    --num-layers 12 \
    --hidden-size 768 \
    --data-path data/my_corpus \
    --seq-length 1024 \
    --micro-batch-size 4 \
    --global-batch-size 64 \
    --train-iters 50000 \
    --lr 1e-4 \
    --save checkpoints/myconvnext

附录:使用中遇到的常见问题

  • 显存 OOM 问题:
    • 解决方案:降 micro-batch、开 --recompute-activations、或加大 TP/PP
  • loss 不收敛 问题:
    • 检查学习率、warmup、初始化;确认 pad_token_id 设置正确
  • 多机多卡时卡死不动:
    • 确认 MASTER_ADDR/MASTER_PORT 一致,NCCL 版本统一等

附录:pretrain 函数的详细说明

  • Megatron-LM 中的 pretrain 函数是模型预训练的核心入口,定义在 megatron/training.py 文件中

pretrain() 函数参数

  • train_valid_test_dataset_provider:用于提供训练、验证和测试数据集的函数或模块
  • model_provider:用于构建模型的函数,调用它可返回模型实例
  • model_type:模型的类型,如ModelType.encoder_or_decoder
  • forward_step_func:定义模型前向传播步骤的函数,包括输入处理、模型计算和损失计算等
  • valid_forward_step_func:可选参数,用于验证阶段的前向传播函数
  • args_defaults:可选参数,包含默认的参数设置

pretrain() 函数内部执行的主要流程

  • 第一步,初始化Megatron :
    • 调用 initialize_megatron 函数,初始化 Megatron-LM 所需的分布式环境,包括设置分布式通信后端、初始化分布式进程组、配置日志记录等
    • 还会调用get_args()与get_timers()函数获取配置参数与计时器,并设置 PyTorch JIT 融合选项,同步启动时间
  • 第二步,设置模型、优化器和学习率计划 :
    • 调用setup_model_and_optimizer函数,传入model_provider和model_type,返回模型、优化器以及学习率调度器
  • 第三步,获取训练/验证/测试数据集 :
    • 根据 args.virtual_pipeline_model_parallel_size 是否为 None 来判断是否需要进行虚拟流水线模型并行处理
    • 如果不进行虚拟流水线模型并行,则直接调用build_train_valid_test_data_iterators函数,获取训练、验证和测试数据迭代器
  • 第四步,调用train函数训练模型 :
    • 判断 args.do_train 和 args.train_iters 是否满足条件,若满足则调用 train 函数执行训练过程
    • train 函数接收多个参数,包括前向传播步骤函数、模型、优化器、学习率调度器、训练数据和验证数据迭代器等,并返回最后一次迭代的索引和到目前为止执行的浮点运算次数

附录:如何修改优化器

  • Megatron-LM 里“指定/切换优化器”有两种主流做法:
    • 第一种:不动源码,靠命令行参数(最简单,官方已内置)
    • 第二种:改源码,注册自定义优化器(想换 Lion、RAdam 等第三方优化器时用)

第一种:命令行直接切换(无需改代码)

  • 注:Megatron 从 2024-10 之后的版本开始,把 optimizer 也暴露成了 CLI 参数:
    主要参数 取值 说明
    --optimizer adam, sgd 默认 adam,会自动选用 Apex 的 FusedAdam
    --adam-beta1 0.9
    --adam-beta2 0.95
    --adam-eps 1e-8
    --sgd-momentum 0.9 只在 --optimizer sgd 时生效
    --weight-decay 0.1 通用
    --clip-grad 1.0 梯度裁剪
  • 示例:把优化器换成 SGD + momentum 的启动脚本如下:
    1
    2
    3
    4
    5
    torchrun --nproc_per_node=8 pretrain_gpt.py \
    ... \
    --optimizer sgd \
    --sgd-momentum 0.9 \
    --weight-decay 1e-4

第二种:源码级自定义优化器(以 Lion 为例)

  • 当你想用官方未内置的优化器(Lion、AdaFactor、RAdam等)时,只要三步:

  • 第一步:在 megatron/core/optimizer/ 里新建文件 lion.py,并定义自己的优化器类

    1
    2
    3
    4
    import torch
    class Lion(torch.optim.Optimizer): # 继承 Optimizer 类
    def __init__(self, params, lr=1e-4, betas=(0.9, 0.99), weight_decay=0.0):
    ...
    • 问题:需要特殊处理的优化器,比如可能涉及其他更多超参数的优化器,还需要考虑
  • 第二步:在 megatron/core/optimizer/__init__.py 的 _get_megatron_optimizer() 中注册新的优化器

    1
    2
    elif opt == 'lion':
    optimizer = Lion(param_groups, lr=args.lr, weight_decay=args.weight_decay)
  • 第三步:启动脚本里添加新的优化器选项

    1
    --optimizer lion
  • Megatron 会自动把上述自定义的 Lion 优化器包装到 DistributedOptimizer(或 DeepSpeed ZeRO,如果启用)里,梯度同步、fp16/bf16 主参数、checkpoint 保存/加载全部复用现有逻辑


附录:CPU Offload & 显存优化

  • Megatron 支持把优化器状态卸载到 CPU 以减少显存:
    1
    2
    --optimizer-cpu-offload \
    --optimizer-offload-fraction 0.8 # 80% 状态放 CPU

附录:Megatron 使用代码简单示例

  • 下面是一个使用 Megatron 训练 GPT 模型的示例代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    import os
    import torch
    from megatron import get_args
    from megatron import mpu
    from megatron.initialize import initialize_megatron
    from megatron.training import setup_model_and_optimizer
    from megatron.model import GPTModel
    from megatron.training import train_step
    from megatron.data.gpt_dataset import build_train_valid_test_datasets

    def main():
    # 初始化Megatron
    initialize_megatron(extra_args_provider=None, args_defaults={
    'tokenizer_type': 'GPT2BPETokenizer',
    'micro_batch_size': 4,
    'global_batch_size': 32,
    'lr': 0.00015,
    'min_lr': 0.00001,
    'lr_decay_style': 'cosine',
    'weight_decay': 0.1,
    'clip_grad': 1.0,
    'lr_warmup_fraction': 0.01,
    'num_layers': 24,
    'hidden_size': 1024,
    'num_attention_heads': 16,
    'seq_length': 1024,
    'max_position_embeddings': 1024,
    'vocab_size': 50257, # GPT-2 vocab size
    'tensor_model_parallel_size': 2,
    'pipeline_model_parallel_size': 2,
    'pipeline_model_parallel_split_rank': 0,
    'fp16': True,
    'bf16': False,
    'seed': 1234,
    })

    args = get_args()

    # 构建数据集
    train_ds, valid_ds, test_ds = build_train_valid_test_datasets(
    data_prefix=args.data_path,
    data_impl=args.data_impl,
    splits_string=args.split,
    train_valid_test_num_samples=[args.train_samples, args.valid_samples, args.test_samples],
    seq_length=args.seq_length,
    seed=args.seed,
    skip_warmup=(not args.mmap_warmup)
    )

    # 设置模型和优化器
    model, optimizer, lr_scheduler = setup_model_and_optimizer()

    # 训练循环
    iteration = 0
    max_iterations = args.train_iters

    while iteration < max_iterations:
    loss = train_step(model, optimizer, lr_scheduler, train_ds)
    if torch.distributed.get_rank() == 0 and iteration % args.log_interval == 0:
    print(f"迭代: {iteration}/{max_iterations}, 损失: {loss.item()}")
    iteration += 1

    if __name__ == "__main__":
    main()
  • 通常使用以下命令启动训练(假设每台机器使用 4 个 GPU):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    # 使用torchrun启动分布式训练
    torchrun --nproc_per_node=4 --master_port=12345 your_script.py \
    --data-path /path/to/your/dataset \
    --vocab-file /path/to/vocab.json \
    --merge-file /path/to/merges.txt \
    --save /path/to/save/checkpoints \
    --load /path/to/load/checkpoints \
    --num-layers 24 \
    --hidden-size 1024 \
    --num-attention-heads 16 \
    --seq-length 1024 \
    --max-position-embeddings 1024 \
    --micro-batch-size 4 \
    --global-batch-size 32 \
    --lr 0.00015 \
    --min-lr 0.00001 \
    --lr-decay-style cosine \
    --lr-warmup-fraction 0.01 \
    --weight-decay 0.1 \
    --clip-grad 1.0 \
    --fp16 \
    --seed 1234

附录:Megatron 中间数据 decode 查看

  • 示例代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    import os
    import torch

    # 读取 tokens.bin(假设为 int32 类型,根据预处理配置调整 dtype)
    tokens_path = "./processed_tokens_document.bin"

    dtype = torch.int32 # 假设是int32,根据实际预处理配置调整(如int64)
    bytes_per_token = dtype.itemsize # int32->4字节,int64->8字节

    # 获取文件总大小(字节)
    file_size = os.path.getsize(tokens_path)
    # 计算总token数(总字节数 / 每个token的字节数)
    total_tokens = file_size // bytes_per_token
    print(f"文件总大小:{file_size} 字节,总token数:{total_tokens}")

    from transformers import AutoModelForCausalLM, AutoTokenizer
    model_name = "./path_to_model/"
    # load the tokenizer and the model
    tokenizer = AutoTokenizer.from_pretrained(model_name)

    # 抽查的 Token 数量
    block_size = 10000

    # 分块读取并解码
    with open(tokens_path, "rb") as f:
    for start in range(0, total_tokens, block_size):
    # 计算当前块的结束位置(不超过总token数)
    end = min(start + block_size, total_tokens)
    current_block_size = end - start

    # 读取当前块的二进制数据(字节数 = token数 × 每个token的字节数)
    f.seek(start * bytes_per_token) # 移动到当前块的起始位置
    block_bytes = f.read(current_block_size * bytes_per_token)

    # 将二进制数据转为torch tensor(token索引)
    block_tokens = torch.frombuffer(block_bytes, dtype=dtype)

    # 解码当前块为文本
    block_text = tokenizer.decode(block_tokens.tolist(), skip_special_tokens=False) # skip_special_tokens=True 会缺失 Special Token

    print(f"处理块 {start//block_size + 1}/{(total_tokens + block_size -1)//block_size}")
    print(block_text)
    break # 打开 break 可不断循环读取

附录:ckpt 文件清理

  • 使用 Megatron-LM 训练模型时,为了保证可恢复,常常会出现存储多个 ckpt 的情况,一般是一定的步骤就存储一个 ckpt
  • 当实验完成后一般仅保留最后一个即可
  • Meagtron-LM 的每个 ckpt 中,都完整存储着从这个 ckpt 启动继续训练所需的所有文件,包括模型权重文件等
    1
    2
    distrib_optim.pt:分布式优化器状态分片,训练恢复时用
    model_optim_rng.pt:随机数生成器状态,训练恢复时用,可能包含模型权重等,根据具体场景可能部分

脚本编写

  • 脚本编写的基本要求为:
    • 删除某个目录(用参数传入)下所有满足条件的文件夹(包括子文件夹):
      • 1)创建日期在 “2024-08-01” 到 “2024-08-10” 之间的
      • 2)文件名以 iter_000x 命名,且x是100 的整倍数,比如 iter_0000600 或 iter_0001000 等
      • 3)当前同级目录下还存在以 iter_000x 命名,且 x 比自己大的文件夹
    • 删除前要求如下:
      • 删除每个文件夹时,先询问是否删除,必须等待回应才继续(同时打印被删除的文件夹及其同级的其他文件夹和文件),Y表示删除,N表示不删除,直接Enter表示不删除;
      • 注意:为了安全起见,一定要收到 “Y” 作为输入再删除,否则不删除,避免误删
  • 下面是一个脚本实现(大模型实现,经过本人部分修改),用于帮助清理 ckpt(已经测试过可以使用):
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    #!/bin/bash

    # 用法: ./clean_x00_steps_in_ckpt.sh /path/to/target_dir
    TARGET_DIR="$1"

    if [ -z "$TARGET_DIR" ]; then
    echo "❌ 请传入目标目录路径"
    exit 1
    fi

    if [ ! -d "$TARGET_DIR" ]; then
    echo "❌ 目录不存在: $TARGET_DIR"
    exit 1
    fi

    # step_interval
    step_interval=100

    # 日期范围
    START_DATE="2025-08-01"
    END_DATE="2025-11-10"

    # 转换为时间戳方便比较
    start_ts=$(date -d "$START_DATE" +%s)
    end_ts=$(date -d "$END_DATE" +%s)

    # 遍历所有匹配 iter_000x 的文件夹(递归)
    find "$TARGET_DIR" -type d -regextype posix-egrep -regex '.*/iter_000[0-9]+' | while read -r dir; do
    basename=$(basename "$dir")

    # 提取数字部分
    num=$(echo "$basename" | sed -E 's/iter_0+([0-9]+)/\1/')

    # 判断是否是 step_interval 的倍数
    if (( num % step_interval != 0 )); then
    continue
    fi

    # 获取创建日期(Linux & macOS兼容)
    if [[ "$OSTYPE" == "darwin"* ]]; then
    create_date=$(stat -f "%SB" -t "%Y-%m-%d" "$dir")
    else
    create_date=$(stat -c %w "$dir")
    if [ "$create_date" = "-" ]; then
    create_date=$(stat -c %y "$dir" | cut -d' ' -f1)
    fi
    fi

    # 转换为时间戳
    create_ts=$(date -d "$create_date" +%s 2>/dev/null)
    if [ -z "$create_ts" ]; then
    continue
    fi

    # 日期范围判断
    if (( create_ts < start_ts || create_ts > end_ts )); then
    continue
    fi

    # 检查同级目录是否存在更大的 iter_000y
    parent_dir=$(dirname "$dir")
    bigger_exist=false
    for sibling in "$parent_dir"/iter_000*; do
    if [ -d "$sibling" ]; then
    sib_num=$(echo "$(basename "$sibling")" | sed -E 's/iter_0+([0-9]+)/\1/')
    if (( sib_num > num )); then
    bigger_exist=true
    break
    fi
    fi
    done

    if [ "$bigger_exist" = false ]; then
    continue
    fi

    # 符合条件 -> 询问是否删除
    # 打印当前目录及同级目录内容
    echo "----------------------------------------"
    echo "📂 待删除文件夹: $dir ✅"
    echo "----------------------------------------"
    echo "同级目录内容:"
    ls -l "$parent_dir"
    echo "----------------------------------------"
    read -p "是否删除? (Y/N, 回车默认不删除): " choice < /dev/tty # 必须使用 < /dev/tty 以确保从交互界面接收到一个输入

    if [[ "$choice" =~ ^[Yy]$ ]]; then
    rm -rf "$dir" && echo "✅ 已删除 $dir"
    else
    echo "跳过 $dir"
    fi
    done

Maven——maven包管理问题


Maven配置

  • maven 默认配置路径为 \~/.m2/settings.xml
  • 默认项目路径为 \~/.m2/repository
  • maven 包版本号检索: https://mvnrepository.com/

手动下载安装 Maven 包

  • 注:某些包无法通过 maven 自动下载
  • 即使他们在 https://mvnrepository.com/ 中能搜到对应版本号
  • 此时一种解决方案是下载包,对应在~/.m2/repository路径下创建包名和版本号相对应的文件夹,并且拷贝jar到对应的路径下
    • 下面是一个例子
      1
      2
      3
      4
      5
      6
      # 包信息
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-thymeleaf</artifactId>
      <version>2.1.6.RELEASE</version>
      # 存储到本地
      ~/.m2/repository/org/springframework/boot/spring-boot-starter-jdbc/2.1.6.RELEASE/spring-boot-starter-jdbc-2.1.6.RELEASE.jar

软件安装——opencv安装


oepncv 与 Python 的关系

  • opencv 的兼容性很差,不同 Python 版本对应不同的 opencv 需要对应安装,否则会卡在最后出现错误(卡很久)

    • 比如 Python 3.6 的 opencv-python 版本安装为4.5.4.60参考链接

      1
      2
      pip install opencv-contrib-python==4.5.4.60
      pip install opencv-python==4.5.4.60
    • 即使是一些项目配置好依赖的时候,往往也要自己手动指定版本号安装opencv,否则不能成功


使用homebrew安装依赖

  • homebrew安装一些依赖时,一些包可能会特别慢(特别是gcc等),可以选择一个个分别安装,反而能加快速度,否则可能出现下载失败导致需要全部重新安装的尴尬情况
    • 有时候不用VPN会更快,可尝试

切换国内镜像

  • 有时候能让homebrew下载更快
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    cd "$(brew --repo)"                                                                          
    git remote set-url origin https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/brew.git

    cd "$(brew --repo)/Library/Taps/homebrew/homebrew-core"
    git remote set-url origin https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/homebrew-core.git

    cd "$(brew --repo)"/Library/Taps/homebrew/homebrew-cask
    git remote set-url origin https://mirrors.ustc.edu.cn/homebrew-cask.git

    brew update

githubusercontent 访问失败问题解决

问题背景

  • 在安装文件时出现错误

    1
    2
    3
    curl: (7) Failed to connect to raw.githubusercontent.com port 443: Connection refused
    Error: opencv: Failed to download resource "cmake.rb"
    Download failed: https://raw.githubusercontent.com/Homebrew/homebrew-core/82f2aac1cfd7295db3e59240729e5f9d74b0ec51/Formula/c/cmake.rb
  • 经尝试,手动打开链接https://raw.githubusercontent.com/Homebrew/homebrew-core/82f2aac1cfd7295db3e59240729e5f9d74b0ec51/Formula/c/cmake.rb也不行

解决方案

  • 修改 DNS 为8.8.8.8即可
    • 该修改可能造成一些网站不能访问,慎用(用后即使修改)

RL——MICRO

本文简单介绍 MICRO 模型

  • 参考链接:
    • 原始论文:MICRO: Model-Based Offline Reinforcement Learning with a Conservative Bellman Operator, UCAS, arXiv 2023.12, IJCAI 2024
    • 使用到 MICRO 文章的工作:Model-based RL自动出价算法的演进之路,阿里妈妈技术

MICRO 基本思想

  • 主要用于解决Offline RL的问题,属于Model-based方法
  • 发表于2023年底,中IJCAI 2024

MICRO 方法详情

  • MICRO训练代码
  • 流程说明:
    • 训练 K 个Critic网络 \(\{Q_{w_i}\}_{i=1}^K\) ,类似 Twin Q 的作用
    • 训练 N 个Dynamics模型 \(\{T_\phi^i\}_{i=1}^N\) ,用于交互合成数据
    • 每次迭代时:
      • 从 \(T_\phi^i\) 中采样得到多步展开,并加入到数据集 \(D_\text{model}\) 中
      • 从混合数据(模型和真实数据混合)中采样
      • 按照指定公式分别更新 Critic 网络和策略网络
      • 软更新目标Q网络
  • 相关变量说明:
    • 关于公式:\(f(s,a) = \max_{a’\in \mathcal{A}}Q(s’,a’) - \inf_{\bar{s} \in \mathcal{X}(s,a)}\Big[ \max_{a’\in \mathcal{A}}Q(\bar{s},a’) \Big]\) ,有:
      $$
      \begin{align}
      s’ &\sim \frac{1}{N}\sum_{i=1}^N T_\phi^i(s’|s,a) \\
      \mathcal{X}(s,a) &= \{s’|s’\sim T_\phi^i(s’|s,a), i=1,2,\cdots,N \}
      \end{align}
      $$
      • 这里使用 \(\inf_{\bar{s} \in \mathcal{X}(s,a)}\Big[ \max_{a’\in \mathcal{A}}Q(\bar{s},a’) \Big]\) 可以用于减少状态 \(s’\) 的不确定性对Q值造成的影响,Model-based RL自动出价算法的演进之路,阿里妈妈技术中有用到类似思想
  • 问题:
    • 如何理解公式12中 \(f(s,a)\) 的作用?
1…6061
Joe Zhou

Joe Zhou

Stay Hungry. Stay Foolish.

608 posts
49 tags
GitHub E-Mail
© 2026 Joe Zhou
Powered by Hexo
|
Theme — NexT.Gemini v5.1.4