<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[扳布屋]]></title><description><![CDATA[Le vent se lève, il faut tenter de vivre]]></description><link>https://bambooo.top</link><image><url>https://blog-vanh.oss-cn-hangzhou.aliyuncs.com/image/logo.png</url><title>扳布屋</title><link>https://bambooo.top</link></image><generator>Yohaku (https://github.com/Innei/Yohaku)</generator><lastBuildDate>Wed, 17 Jun 2026 07:52:58 GMT</lastBuildDate><atom:link href="https://bambooo.top/feed" rel="self" type="application/rss+xml"/><pubDate>Wed, 17 Jun 2026 07:52:58 GMT</pubDate><language><![CDATA[zh-CN]]></language><item><title><![CDATA[奇怪的惰性]]></title><description><![CDATA[<div><blockquote>此渲染由 Yohaku API 生成，或存排版之虞，最佳体验请往：<a href="https://bambooo.top/notes/5">https://bambooo.top/notes/5</a></blockquote><div><p>我上一份工作，也是大学毕业后的第一份工作，在深圳南山某不知名科技公司，部门加班文化严重，组内更是严重（主要是由于那个恶心的组长喜欢无差别PUA所有组员导致，这个事我想可以单开一篇来讲），周内八九点下班是常态，周六日加班也不在少数，那会头痛的频率非常高，但是，哪怕是这种状况下，周六日还是会逼着自己学点东西，总认为不进步就会落后，后面就找不到好工作，一系列想法让自己变得很焦。</p><p>辞职后，在家休息了一小段时间，开始投递简历，最终选择了一家北京的公司（很大一部分原因是有两个大学室友都在北京，有玩伴），这家公司面试结束后hr就说明了加班很少，几乎不加班，来了之后也确实如此，工作半年了，除了部分时候觉得工作完成的不满意主动带电脑回家办公，其余时候都是到点下班。奇怪的是，在这种空闲时间很多的情况下，反而不愿意主动学习了（当然，也不是完全不学习，只是主动性少很多），脑子里也有了很多摆烂似的想法，比如“这样也挺好的，为什么非要一直进步呢，能养好自己，每个月存点钱，开开心心”。</p><p>高压情况下，反而促进自己主动学习，现在想来，当时的心态，很大部分原因是觉得学了更多东西，能去到更好的企业，逃离那个让自己产生大量负面情绪的地方。</p></div><p style="text-align:right"><a href="https://bambooo.top/notes/5#comments">览毕，何不一言？</a></p></div>]]></description><link>https://bambooo.top/notes/5</link><guid isPermaLink="true">https://bambooo.top/notes/5</guid><dc:creator><![CDATA[扳布]]></dc:creator><pubDate>Mon, 15 Jun 2026 06:57:04 GMT</pubDate></item><item><title><![CDATA[博客增加友圈]]></title><description><![CDATA[<link rel="preload" as="image" href="https://bambooo.top/api/v3/objects/image/dyfpvc96lpr2m503o8.png"/><div><blockquote>此渲染由 Yohaku API 生成，或存排版之虞，最佳体验请往：<a href="https://bambooo.top/posts/fiddle/circle">https://bambooo.top/posts/fiddle/circle</a></blockquote><div><p>对于博客，还是希望随时能看到网上的朋友们最近的新动态，于是vibe coding了一个友圈页面。</p><p>之前一直通过github action根据innei的yohaku仓库构建部署，这种方法就是省事，作者的新功能能及时更新，缺点就是无法客制化，所以还是clone了一份以便定制化修改，这个友圈页面一直想加上，得益于现在的agent足够强大，一天时间就可以实现一个不错的效果。</p><p><img height="997" src="https://bambooo.top/api/v3/objects/image/dyfpvc96lpr2m503o8.png" width="1895"/></p></div><p style="text-align:right"><a href="https://bambooo.top/posts/fiddle/circle#comments">览毕，何不一言？</a></p></div>]]></description><link>https://bambooo.top/posts/fiddle/circle</link><guid isPermaLink="true">https://bambooo.top/posts/fiddle/circle</guid><dc:creator><![CDATA[扳布]]></dc:creator><pubDate>Fri, 05 Jun 2026 08:34:55 GMT</pubDate></item><item><title><![CDATA[如何管理上下文]]></title><description><![CDATA[<div><blockquote>此渲染由 Yohaku API 生成，或存排版之虞，最佳体验请往：<a href="https://bambooo.top/posts/think/context_manage">https://bambooo.top/posts/think/context_manage</a></blockquote><div><h2 id="">前言</h2><p>日常使用编程 Agent 的时候，总会考虑到上下文窗口的大小，首先，不同厂商模型的上下文长度不一样，我日常使用 Claude Code（以下简称cc），搭配 GLM5.1、Deepseek-v4-pro，前者支持最大上下文窗口200k，后者能支持长达1M。</p><p>对其他领域不太了解，拿编程来说，当你上下文达到1M，前期任务对话的注意力已经被稀释了，200k的上下文超出后，在经过多次compact后，早期内容也会被截断，而且还会有幻觉累计的风险，长会话中如果某个错误理解没被纠正，后续会在错误基础上叠加；再者，一个会话只能做一件事，无法并行（当然，并不是说短会话就可以并行，还得看你的项目是否足够模块化，各层之间边界是否清晰），可见一直在一个会话中对话的性价比并不高。那是不是应该一个任务开一个会话呢，当然也不是，这种做法很容易想到的一点就是，重复喂上下文，每次都要重新解释项目背景、架构、约定...，效率极低，而且缺乏连贯性，模型不知道之前做了哪些决策，容易走回头路甚至错路。</p><h2 id="">可参考方案</h2><blockquote><p>核心思路是：用文件代替会话记忆，上下文存在于代码库本身，而不是对话历史里</p></blockquote>
<h3 id="-claudemd">维护好 CLAUDE.md</h3><p>众所周知，cc有一个很核心的文件 —— <code>CLAUDE.MD</code>，你在项目根目录下进入cc，执行<code>/init</code>，大模型就会读取项目内容自动创建这个文件</p><p>如果你的项目已经有框架了，那么直接让大模型生成会比较省事，如果是新项目，则需要你手动编写好背景、架构、约束等</p><p>这个文件不仅是一个项目说明，更是一个项目记忆文件，cc每次启动都会读取它，建议 <strong>每完成一个阶段任务就更新这个文件</strong>，新会话一开始就有完整上下文，这一步也可以让大模型来帮你做，不过还是建议 review 一下</p><h3 id="">按&quot;工作单元&quot;切割会话，而不是按功能</h3><p>不是&quot;每个功能开一个会话&quot;，而是一个聚焦的任务开一个会话：</p><pre class="language-markdown lang-markdown"><code class="language-markdown lang-markdown">✅ 好的会话边界
- &quot;实现订单创建的 API + service + 数据库 migration&quot;
- &quot;修复购物车计算逻辑的 3 个 bug&quot;
- &quot;重构用户模块，拆分成更小的 service&quot;

❌ 不好的边界  
- &quot;帮我开发整个电商后台&quot;（太大）
- &quot;改一行代码&quot;（太小，不值得切换）
</code></pre>
<h3 id="-prompt-">第一个 prompt 规范好边界</h3><p>新会话开头不要直接说&quot;帮我写XXX&quot;，而是：</p><pre class="language-markdown lang-markdown"><code class="language-markdown lang-markdown">背景：我在开发电商项目（Next.js + PostgreSQL），CLAUDE.md 里有架构细节。
当前任务：实现订单状态流转逻辑（待付款→已付款→已发货→已完成）
相关文件：/app/api/orders/, /lib/services/orderService.ts
约束：状态流转需要记录操作日志，不能直接改 status 字段
</code></pre>
<h3 id="-clear-">利用 /clear 而不是开新窗口</h3><p>当一个任务完成、要切换到另一个任务时，在同一个 Claude Code 实例里用 /clear 清空对话历史，项目文件仍然在，只清掉对话噪音，比重开一个会话更高效。</p>
<h2 id="">总结</h2><p>核心其实就是一句话，<strong>上下文应该活在代码和文档里，而不是活在对话历史里。</strong></p></div><p style="text-align:right"><a href="https://bambooo.top/posts/think/context_manage#comments">览毕，何不一言？</a></p></div>]]></description><link>https://bambooo.top/posts/think/context_manage</link><guid isPermaLink="true">https://bambooo.top/posts/think/context_manage</guid><dc:creator><![CDATA[扳布]]></dc:creator><pubDate>Mon, 01 Jun 2026 06:49:28 GMT</pubDate></item><item><title><![CDATA[IOS快捷指令 + 飞书 + LLM全自动记账]]></title><description><![CDATA[<link rel="preload" as="image" href="https://bambooo.top/api/v3/objects/image/20260531163535633.gif"/><div><blockquote>此渲染由 Yohaku API 生成，或存排版之虞，最佳体验请往：<a href="https://bambooo.top/posts/fiddle/record_llm">https://bambooo.top/posts/fiddle/record_llm</a></blockquote><div><h2 id="tldr">TLDR</h2><p>24年有个挺火的通过IOS快捷指令 + 飞书（或者Number表格）实现自动记账</p><p><a href="https://www.bilibili.com/video/BV1Wf421B7JY/?spm_id_from=333.337.search-card.all.click&amp;vd_source=71450b2ba3f42305b121d0eff7f875a8">https://www.bilibili.com/video/BV1Wf421B7JY/?spm_id_from=333.337.search-card.all.click&amp;vd_source=71450b2ba3f42305b121d0eff7f875a8</a></p><p>我也跟着实现了一下，用到了现在，整个过程其实很方便了，通过IOS的OCR提取文字，再通过正则提取出数字，手动选择收支类型，用途或者来源。</p><h2 id="">改进</h2><p>但是昨天想到，可以通过接入LLM，提取出的文字也不再通过正则来提取了，直接把所有文字扔给大模型，让它来提取我们需要的内容（金额、收支类型、用途、备注），以json返回，我们再将json通过飞书接口保存，真正实现自动记账，效果如下</p>
<p><img alt="全自动记账" src="https://bambooo.top/api/v3/objects/image/20260531163535633.gif"/></p><p>可以看到，整个过程只需要确定金额是正确的，这一步其实也可以省掉，现在的大模型足够强，根据一些关键信息能推断出实付金额，比如这个案例中，能识别出是111.5，我用的glm4.7，实际消费也非常之低，一次差不多就一分钱左右，如果不是收支很频繁，一块钱够用几个月</p></div><p style="text-align:right"><a href="https://bambooo.top/posts/fiddle/record_llm#comments">览毕，何不一言？</a></p></div>]]></description><link>https://bambooo.top/posts/fiddle/record_llm</link><guid isPermaLink="true">https://bambooo.top/posts/fiddle/record_llm</guid><dc:creator><![CDATA[扳布]]></dc:creator><pubDate>Sun, 31 May 2026 08:39:23 GMT</pubDate></item><item><title><![CDATA[Yohaku 部署]]></title><description><![CDATA[<div><blockquote>此渲染由 Yohaku API 生成，或存排版之虞，最佳体验请往：<a href="https://bambooo.top/posts/fiddle/deploy_yohaku">https://bambooo.top/posts/fiddle/deploy_yohaku</a></blockquote><div><blockquote><p>以下为 Shiro 闭源版本 Yohaku 部署方案，可能随着版本更新而不再适用</p></blockquote>
<h2 id="-1panel">安装 1Panel</h2><pre class=""><code class="">curl -sSL https://resource.fit2cloud.com/1panel/package/quick_start.sh -o quick_start.sh &amp;&amp; sudo bash quick_start.sh
</code></pre>
<p>配置镜像输入 y（安装openresty需要），其他一律 enter 即可</p><p>安装完成后，打开1panel网址，进入应用商店，安装openresty</p><h2 id="">后端部署</h2><blockquote><p>正常需要安装 docker，不过由于 1Panel 面板默认会安装 docker，所以这里不需要，如果使用宝塔面板或其他方式，请自行安装docker</p></blockquote>
<h3 id="">拉取并修改配置文件</h3><pre class="language-bash lang-bash"><code class="language-bash lang-bash">#回到根目录并创建mx-space/core的文件夹，并跳转至core文件夹下
cd &amp;&amp; mkdir -p mx-space/core &amp;&amp; cd $_ 
# 拉取 docker-compose.yml 文件
wget https://fastly.jsdelivr.net/gh/mx-space/core@master/docker-compose.yml
</code></pre>
<p>主要修改<code>ALLOWED_origins</code>和<code>JWT_SECRET</code></p><pre class="language-yml lang-yml"><code class="language-yml lang-yml"># Shared env block — mx-migrate boots a full Nest context (it now runs the
# combined schema + app-data migration phases), so it must receive the same
# config the runtime app needs (Redis, snowflake worker id, jwt secret, …).
x-mx-env: &amp;mx-env
  TZ: Asia/Shanghai
  NODE_ENV: production
  REDIS_HOST: redis
  PG_HOST: postgres
  PG_PORT: &quot;5432&quot;
  PG_USER: mx
  PG_PASSWORD: mx
  PG_DATABASE: mx_core
  SNOWFLAKE_WORKER_ID: &quot;1&quot;
  ALLOWED_ORIGINS: bambooo.top,localhost:*,127.0.0.1:*
  JWT_SECRET: xxxxxxxx

services:
  app:
    container_name: mx-server
    image: innei/mx-server:latest
    environment: *mx-env
    volumes:
      - ./data/mx-space:/root/.mx-space
    ports:
      - &#x27;2333:2333&#x27;
    depends_on:
      mx-migrate:
        condition: service_completed_successfully
      redis:
        condition: service_started
    networks:
      - mx-space
    restart: unless-stopped
    healthcheck:
      test: [CMD, curl, -f, &#x27;http://127.0.0.1:2333/api/v3/health&#x27;]
      interval: 1m30s
      timeout: 30s
      retries: 5
      start_period: 30s

  # One-shot release-phase migration runner. Runs schema migrations (drizzle)
  # then app-data migrations (Nest-bootstrapped registry). Exits 0; the `app`
  # service waits for completion via `service_completed_successfully` before
  # starting. See docs/superpowers/specs/2026-05-05-database-migration-release-phase-design.md.
  mx-migrate:
    container_name: mx-migrate
    image: innei/mx-server:latest
    command: [&#x27;node&#x27;, &#x27;migrate.mjs&#x27;]
    environment: *mx-env
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_started
    networks:
      - mx-space
    restart: &#x27;no&#x27;

  postgres:
    container_name: postgres
    image: postgres:16-alpine
    environment:
      - POSTGRES_USER=mx
      - POSTGRES_PASSWORD=mx
      - POSTGRES_DB=mx_core
    volumes:
      - ./data/postgres:/var/lib/postgresql/data
    healthcheck:
      test: [&#x27;CMD-SHELL&#x27;, &#x27;pg_isready -U mx -d mx_core&#x27;]
      interval: 30s
      timeout: 5s
      retries: 5
      start_period: 10s
    networks:
      - mx-space
    restart: unless-stopped

  redis:
    image: redis:alpine
    container_name: redis
    volumes:
      - ./data/redis:/data
    healthcheck:
      test: [CMD-SHELL, &#x27;redis-cli ping | grep PONG&#x27;]
      start_period: 20s
      interval: 30s
      retries: 5
      timeout: 3s
    networks:
      - mx-space
    restart: unless-stopped

networks:
  mx-space:
    driver: bridge
</code></pre>
<h3 id="-core">启动 core</h3><pre class="language-bash lang-bash"><code class="language-bash lang-bash">docker compose up -d
</code></pre>
<p>这里需要几分钟时间，耐心等待</p><h2 id="">网站创建</h2><blockquote><p>以下步骤均在 1Panel 中进行</p></blockquote>
<h3 id="">证书申请</h3><p>先创建Acme账户，账号类型建议选ZeroSSL，省事</p><p>创建完后申请证书 → 填写主域名 → Acme账户 → 秘钥算法（EC 256） → 验证方式（HTTP） → 自动续签 → 确认</p><h3 id="">创建网站</h3><p>创建 → 静态网站 → 监听IPV6 → 配置 → HTTPS（开启，选择刚才的证书） → 配置文件（将下面内容插入到中间）</p><p>我是单域名配置，如果你是双域名，参考官方文档</p><p> <a href="https://mx-space.js.org/docs/deploy/reverse-proxy#%E5%8F%8C%E5%9F%9F%E5%90%8D%E9%85%8D%E7%BD%AE">双域名配置</a></p><pre class="language-nginx lang-nginx"><code class="language-nginx lang-nginx"># WebSocket 地址
location /socket.io {
    proxy_set_header Upgrade $http_upgrade; 
    proxy_set_header Connection &quot;Upgrade&quot;; 
    proxy_buffering off; 
    proxy_set_header Host $host; 
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 
    proxy_set_header X-Forwarded-Proto $scheme; 
    proxy_pass http://127.0.0.1:2333/socket.io; 
}
# API 地址
location /api/v3 {
    proxy_pass http://127.0.0.1:2333/api/v3; 
}
# 简读 render 地址
location /render {
    proxy_pass http://127.0.0.1:2333/render; 
}
# Shiro 地址
location / {
    proxy_pass http://127.0.0.1:2323; 
}
# 后台地址
location /proxy {
    proxy_pass http://127.0.0.1:2333/proxy; 
}
location /qaqdmin { #如果需要修改自己的后台地址可以改这里
    proxy_pass http://127.0.0.1:2333/proxy/qaqdmin; 
}
</code></pre>
<p>修改完毕，点击保存并重载</p><p>访问 <a href="https://你的域名/qaqdmin">https://你的域名/qaqdmin</a> 就可以正常进入博客后台（如果碰到问题也不要急，可以在评论区留下你的问题，或许可以给你提供一些帮助），然后就是简单的填写博客基本信息</p><h2 id="">前端部署</h2><blockquote><p>这一步中间可能会出现各种错误，建议在服务器上安装 Claude Code，让大模型帮你分析排查问题，一般都能解决</p></blockquote>
<p>我采用的是 Innei 提供的自动部署工作流方式</p><h3 id="fork">Fork</h3><p><a href="https://github.com/innei-dev/yohaku-deploy-action">yohaku-deploy-action</a></p><p>打开github，fork上面的仓库</p><h3 id="-secrets">设置 Secrets</h3><p>打开仓库，进入 Settings → Secrets and variables → Actions → Repository secrets → 挨个添加下面的参数</p><ul><li><code>HOST</code> 服务器地址</li><li><code>USER</code> 服务器用户名</li><li><code>PASSWORD</code> 服务器密码</li><li><code>PORT</code> 服务器 SSH 端口</li><li><code>KEY</code> 服务器 SSH Key（可选，密码 key 二选一）</li><li><code>GH_PAT</code> 可访问之前 repository 内填写仓库的 Github Token</li></ul><h3 id="-env">配置 .env</h3><p>在服务器 root 下创建 yohaku 文件夹，并在其下创建 .env</p><pre class="language-bash lang-bash"><code class="language-bash lang-bash">cd root/
mkdir yohaku
cd yohaku/
touch .env
vim .env
</code></pre>
<p>填写以下内容</p><pre class="language-ENV lang-ENV"><code class="language-ENV lang-ENV">BASE_URL=

NEXT_PUBLIC_API_URL=
NEXT_PUBLIC_CLIENT_API_URL=
NEXT_PUBLIC_GATEWAY_URL=

NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=
CLERK_SECRET_KEY=

NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in
NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up
NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=/
NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=/

TMDB_API_KEY=

GH_TOKEN=
API_URL=
</code></pre>
<h3 id="-deploy">修改 deploy</h3><p>打开 <code>.github/workflows/deploy.yml</code> 进行修改</p><pre class="language-yml lang-yml"><code class="language-yml lang-yml">name: Build and Deploy

on:
  push:
    branches:
      - main
  # schedule:
  #   - cron: &#x27;0 3 * * *&#x27;

  repository_dispatch:
    types: [trigger-workflow]

permissions: write-all
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

env:
  HASH_FILE: build_hash
  SOURCE_REPO: innei-dev/Yohaku
  BUILD_COMMAND: pnpm --filter @yohaku/web build:ci
  STANDALONE_SUBPATH: standalone/apps/web

jobs:
  prepare:
    name: Prepare
    runs-on: ubuntu-latest
    if: ${{ github.event.head_commit.message != &#x27;Update hash file&#x27; }}

    outputs:
      hash_content: ${{ steps.read_hash.outputs.hash_content }}

    steps:
      - name: Checkout
        uses: actions/checkout@v4
      - name: Read HASH_FILE content
        id: read_hash
        run: |
          content=$(cat ${{ env.HASH_FILE }}) || true
          echo &quot;hash_content=$content&quot; &gt;&gt; &quot;$GITHUB_OUTPUT&quot;
  check:
    name: Check Should Rebuild
    runs-on: ubuntu-latest
    needs: prepare
    outputs:
      canceled: ${{ steps.use_content.outputs.canceled }}

    steps:
      - uses: actions/checkout@v4
        with:
          repository: innei-dev/Yohaku
          token: ${{ secrets.GH_PAT }} # `GH_PAT` is a secret that contains your PAT
          fetch-depth: 0
          lfs: true
          submodules: recursive

      - name: Use content from prev job and compare
        id: use_content
        env:
          FILE_HASH: ${{ needs.prepare.outputs.hash_content }}
        run: |
          file_hash=$FILE_HASH
          current_hash=$(git rev-parse --short HEAD)
          echo &quot;File Hash: $file_hash&quot;
          echo &quot;Current Git Hash: $current_hash&quot;
          if [ &quot;$file_hash&quot; == &quot;$current_hash&quot; ]; then
            echo &quot;Hashes match. Stopping workflow.&quot;
            echo &quot;canceled=true&quot; &gt;&gt; $GITHUB_OUTPUT
          else
            echo &quot;Hashes do not match. Continuing workflow.&quot;
          fi

  build:
    name: Build artifact
    runs-on: ubuntu-latest
    needs: check
    if: ${{needs.check.outputs.canceled != &#x27;true&#x27;}}

    strategy:
      matrix:
        node-version: [lts/*]
    outputs:
      sha_short: ${{ steps.store.outputs.sha_short }}
      branch: ${{ steps.store.outputs.branch }}
    steps:
      - uses: actions/checkout@v4
        with:
          repository: ${{ env.SOURCE_REPO }}
          token: ${{ secrets.GH_PAT }} # `GH_PAT` is a secret that contains your PAT
          fetch-depth: 0
          lfs: true
          submodules: recursive

      - name: Checkout LFS objects
        run: git lfs checkout
      - uses: pnpm/action-setup@v2

      - name: Use Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
          cache: &#x27;pnpm&#x27;
      - uses: jongwooo/next-cache@v1
      - name: Install dependencies
        run: pnpm install
      - name: Build project
        env:
          BASE_URL: ${{ secrets.BASE_URL }}
          NEXT_PUBLIC_API_URL: ${{ secrets.NEXT_PUBLIC_API_URL }}
          NEXT_PUBLIC_GATEWAY_URL: ${{ secrets.NEXT_PUBLIC_GATEWAY_URL }}
        run: |
          ${{ env.BUILD_COMMAND }}
          cd apps/web/.next
          rm -rf cache
          cp -r ../public ./standalone/public
          mv ./static ./standalone/apps/web/.next/static
          cd ./standalone
          echo &#x27;;process.title = &quot;Yohaku (NextJS)&quot;&#x27; &gt;&gt; server.js
          cd &quot;$GITHUB_WORKSPACE&quot;
          mkdir -p assets
          rm -f assets/release.zip
          (cd apps/web/.next &amp;&amp; zip --symlinks -r &quot;$GITHUB_WORKSPACE/assets/release.zip&quot; ./* -x &quot;dev/*&quot; -x &quot;cache/*&quot;)

      # - uses: actions/upload-artifact@v4
      #   with:
      #     name: dist
      #     path: assets/release.zip
      #     retention-days: 7
      - name: Cache Build Artifacts
        id: cache-primes
        uses: actions/cache/save@v4
        with:
          path: assets
          key: ${{ github.run_number }}-release

      - name: Store artifact commit version
        shell: bash
        id: store
        run: |
          sha_short=$(git rev-parse --short HEAD)
          branch_name=$(git rev-parse --abbrev-ref HEAD)
          echo &quot;sha_short=$sha_short&quot; &gt;&gt; &quot;$GITHUB_OUTPUT&quot;
          echo &quot;branch=$branch_name&quot; &gt;&gt; &quot;$GITHUB_OUTPUT&quot;

  deploy:
    name: Deploy artifact
    runs-on: ubuntu-latest
    needs: build
    steps:
      - uses: actions/checkout@v4
      
      - uses: actions/cache/restore@v4
        with:
          path: assets
          key: ${{ github.run_number }}-release

      - name: Restore cached Build Artifacts
        id: cache-primes-restore
        uses: actions/cache/restore@v4
        with:
          path: |
            assets
          key: ${{ github.run_number }}-release
      - name: Move assets to root
        run: mv assets/release.zip release.zip

      - name: Prepare standalone PM2 config
        run: cp pm2/ecosystem.config.js ecosystem.config.js

      - name: copy file via ssh password
        uses: appleboy/scp-action@v0.1.7
        with:
          host: ${{ secrets.HOST }}
          username: ${{ secrets.USER }}
          password: ${{ secrets.PASSWORD }}
          key: ${{ secrets.KEY }}
          port: ${{ secrets.PORT }}
          source: &#x27;release.zip&#x27;
          target: &#x27;/tmp/yohaku&#x27;

      - name: copy PM2 config via ssh password
        uses: appleboy/scp-action@v0.1.7
        with:
          host: ${{ secrets.HOST }}
          username: ${{ secrets.USER }}
          password: ${{ secrets.PASSWORD }}
          key: ${{ secrets.KEY }}
          port: ${{ secrets.PORT }}
          source: &#x27;ecosystem.config.js&#x27;
          target: &#x27;/tmp/yohaku&#x27;

      - name: Exec deploy script with SSH
        uses: appleboy/ssh-action@master

        with:
          command_timeout: 5m
          host: ${{ secrets.HOST }}
          username: ${{ secrets.USER }}
          password: ${{ secrets.PASSWORD }}
          key: ${{ secrets.KEY }}
          port: ${{ secrets.PORT }}
          script: |
            set -e
            source $HOME/.bashrc
            basedir=$HOME/yohaku
            workdir=$basedir/${{ github.run_number }}
            mkdir -p $workdir
            mkdir -p $basedir/.cache
            mv /tmp/yohaku/release.zip $workdir/release.zip
            install -m 644 /tmp/yohaku/ecosystem.config.js $basedir/ecosystem.config.js
            cd $workdir
            unzip -qq -o $workdir/release.zip
            rm -r /tmp/yohaku
            rm -rf $workdir/${{ env.STANDALONE_SUBPATH }}/.env
            ln -s $HOME/yohaku/.env $workdir/${{ env.STANDALONE_SUBPATH }}/.env
            export NEXT_SHARP_PATH=$(npm root -g)/sharp
            # https://github.com/Unitech/pm2/issues/3054
            # symlink workdir node entry file to basedir
            ln -sf $workdir/${{ env.STANDALONE_SUBPATH }}/server.js $basedir/server.js
            mkdir -p $workdir/${{ env.STANDALONE_SUBPATH }}/.next
            rm -rf $workdir/${{ env.STANDALONE_SUBPATH }}/.next/cache
            ln -sf $basedir/.cache $workdir/${{ env.STANDALONE_SUBPATH }}/.next/cache
            cd $basedir
            if pm2 describe Yohaku &gt;/dev/null 2&gt;&amp;1; then
              pm2 reload ecosystem.config.js --update-env
            else
              pm2 start ecosystem.config.js --update-env
            fi
            rm $workdir/release.zip
            pm2 save
            echo &quot;Deployed successfully&quot;

      - name: After deploy script
        run: |
          hash=${{ needs.build.outputs.sha_short }}
          # curl -X &quot;POST&quot; &quot;https://mx.innei.in/api/v2/fn/shiro/new-version-hook&quot; -H &#x27;Content-Type: application/json&#x27; -d &quot;{\&quot;hash\&quot;: \&quot;$hash\&quot;, \&quot;key\&quot;: \&quot;\&quot;}&quot;
          ${{ secrets.AFTER_DEPLOY_SCRIPT }}
  store:
    name: Store artifact commit version
    runs-on: ubuntu-latest
    needs: [deploy, build]
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          persist-credentials: false # otherwise, the token used is the GITHUB_TOKEN, instead of your personal token
          fetch-depth: 0 # otherwise, you will failed to push refs to dest repo
      # Get the commit version from the build job
      - name: Use outputs from build
        env:
          SHA_SHORT: ${{ needs.build.outputs.sha_short }}
          BRANCH: ${{ needs.build.outputs.branch }}
        run: |
          echo &quot;SHA Short from build: $SHA_SHORT&quot;
          echo &quot;Branch from build: $BRANCH&quot;
      - name: Write hash to file
        env:
          SHA_SHORT: ${{ needs.build.outputs.sha_short }}

        run: echo $SHA_SHORT &gt; ${{ env.HASH_FILE }}
      - name: Commit files
        run: |
          git config --local user.email &quot;41898282+github-actions[bot]@users.noreply.github.com&quot;
          git config --local user.name &quot;github-actions[bot]&quot;
          git add ${{ env.HASH_FILE }}
          git commit -a -m &quot;Update hash file&quot;
      - name: Push changes
        uses: ad-m/github-push-action@master
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          branch: ${{ github.ref }}
</code></pre>
<p>如果直接使用 Innei 的部署过程中出现报错，可以参考我的进行修改，或者询问大模型，github自带的 Copilot 也挺好用的</p><p>改完之后 commit，会自动跑，可以进入<code>Actions</code>查看流程</p><h2 id="">配置云函数</h2><p>进入博客后台，附加功能 → 配置与云函数</p><p>创建一条 <code>theme</code> 引用、名称为 <code>shiro</code> 的配置项（数据类型 JSON 或 YAML），这里贴上官方提供的案例</p><pre class="language-json lang-json"><code class="language-json lang-json">{
  &quot;footer&quot;: {
    &quot;otherInfo&quot;: {
      &quot;date&quot;: &quot;2020-{{now}}&quot;,
      &quot;icp&quot;: {
        &quot;text&quot;: &quot;萌 ICP 备 20236136 号&quot;,
        &quot;link&quot;: &quot;https://icp.gov.moe/?keyword=20236136&quot;
      }
    },
    &quot;linkSections&quot;: [
      {
        &quot;name&quot;: &quot;关于&quot;,
        &quot;links&quot;: [
          { &quot;name&quot;: &quot;关于本站&quot;, &quot;href&quot;: &quot;/about-site&quot; },
          { &quot;name&quot;: &quot;关于我&quot;, &quot;href&quot;: &quot;/about&quot; },
          {
            &quot;name&quot;: &quot;关于此项目&quot;,
            &quot;href&quot;: &quot;https://github.com/Innei/Yohaku&quot;,
            &quot;external&quot;: true
          }
        ]
      },
      {
        &quot;name&quot;: &quot;更多&quot;,
        &quot;links&quot;: [
          { &quot;name&quot;: &quot;时间线&quot;, &quot;href&quot;: &quot;/timeline&quot; },
          { &quot;name&quot;: &quot;友链&quot;, &quot;href&quot;: &quot;/friends&quot; }
        ]
      },
      {
        &quot;name&quot;: &quot;联系&quot;,
        &quot;links&quot;: [
          { &quot;name&quot;: &quot;写留言&quot;, &quot;href&quot;: &quot;/message&quot; },
          { &quot;name&quot;: &quot;GitHub&quot;, &quot;href&quot;: &quot;https://github.com/innei&quot;, &quot;external&quot;: true }
        ]
      }
    ]
  },
  &quot;config&quot;: {
    &quot;color&quot;: {
      &quot;light&quot;: [&quot;#33A6B8&quot;, &quot;#FF6666&quot;, &quot;#26A69A&quot;, &quot;#fb7287&quot;, &quot;#69a6cc&quot;],
      &quot;dark&quot;: [&quot;#F596AA&quot;, &quot;#A0A7D4&quot;, &quot;#ff7b7b&quot;, &quot;#99D8CF&quot;, &quot;#838BC6&quot;]
    },
    &quot;site&quot;: {
      &quot;favicon&quot;: &quot;/favicon.svg&quot;,
      &quot;faviconDark&quot;: &quot;/favicon-dark.svg&quot;
    },
    &quot;hero&quot;: {
      &quot;title&quot;: {
        &quot;template&quot;: [
          {
            &quot;type&quot;: &quot;span&quot;,
            &quot;text&quot;: &quot;Hi, I&#x27;m &quot;,
            &quot;style&quot;: { &quot;fontWeight&quot;: 300, &quot;opacity&quot;: 0.85 }
          },
          {
            &quot;type&quot;: &quot;span&quot;,
            &quot;text&quot;: &quot;Innei&quot;,
            &quot;style&quot;: {
              &quot;fontWeight&quot;: 500,
              &quot;color&quot;: &quot;var(--color-accent)&quot;,
              &quot;letterSpacing&quot;: &quot;-0.02em&quot;
            }
          },
          {
            &quot;type&quot;: &quot;span&quot;,
            &quot;text&quot;: &quot; 👋&quot;,
            &quot;style&quot;: {
              &quot;fontWeight&quot;: 300,
              &quot;display&quot;: &quot;inline-block&quot;,
              &quot;transform&quot;: &quot;rotate(-8deg)&quot;
            }
          },
          { &quot;type&quot;: &quot;br&quot; },
          {
            &quot;type&quot;: &quot;span&quot;,
            &quot;text&quot;: &quot;A NodeJS Full Stack &quot;,
            &quot;style&quot;: { &quot;fontWeight&quot;: 300, &quot;opacity&quot;: 0.8 }
          },
          {
            &quot;type&quot;: &quot;code&quot;,
            &quot;text&quot;: &quot;&lt;Developer /&gt;&quot;,
            &quot;style&quot;: {
              &quot;display&quot;: &quot;inline-block&quot;,
              &quot;fontFamily&quot;: &quot;var(--font-mono)&quot;,
              &quot;fontSize&quot;: &quot;0.72em&quot;,
              &quot;fontWeight&quot;: 500,
              &quot;padding&quot;: &quot;0.25em 0.55em&quot;,
              &quot;borderRadius&quot;: &quot;0.35em&quot;,
              &quot;backgroundColor&quot;: &quot;color-mix(in srgb, var(--color-accent) 10%, transparent)&quot;,
              &quot;color&quot;: &quot;var(--color-accent)&quot;,
              &quot;border&quot;: &quot;1px solid color-mix(in srgb, var(--color-accent) 22%, transparent)&quot;
            }
          },
          {
            &quot;type&quot;: &quot;span&quot;,
            &quot;style&quot;: {
              &quot;display&quot;: &quot;inline-block&quot;,
              &quot;width&quot;: &quot;2px&quot;,
              &quot;height&quot;: &quot;0.9em&quot;,
              &quot;backgroundColor&quot;: &quot;var(--color-accent)&quot;,
              &quot;marginLeft&quot;: &quot;2px&quot;,
              &quot;animation&quot;: &quot;blink 1.2s linear infinite&quot;
            }
          }
        ]
      },
      &quot;description&quot;: &quot;An independent developer coding with love.&quot;
    },
    &quot;module&quot;: {
      &quot;activity&quot;: {
        &quot;enable&quot;: true,
        &quot;endpoint&quot;: &quot;/fn/ps/update&quot;
      },
      &quot;donate&quot;: {
        &quot;enable&quot;: false,
        &quot;link&quot;: &quot;&quot;,
        &quot;qrcode&quot;: []
      },
      &quot;bilibili&quot;: {
        &quot;liveId&quot;: 0
      },
      &quot;openpanel&quot;: {
        &quot;enable&quot;: false,
        &quot;id&quot;: &quot;&quot;,
        &quot;url&quot;: &quot;&quot;
      },
      &quot;posts&quot;: {
        &quot;mode&quot;: &quot;loose&quot;
      },
      &quot;signature&quot;: {
        &quot;svg&quot;: &quot;&quot;,
        &quot;animated&quot;: true
      }
    }
  }
}
</code></pre>
<p>修改好后保存</p><p>都完成后，访问前端，没问题的话，恭喜你部署成功</p></div><p style="text-align:right"><a href="https://bambooo.top/posts/fiddle/deploy_yohaku#comments">览毕，何不一言？</a></p></div>]]></description><link>https://bambooo.top/posts/fiddle/deploy_yohaku</link><guid isPermaLink="true">https://bambooo.top/posts/fiddle/deploy_yohaku</guid><dc:creator><![CDATA[扳布]]></dc:creator><pubDate>Fri, 29 May 2026 06:21:51 GMT</pubDate></item><item><title><![CDATA[Shiro/Yohaku 个人状态报错]]></title><description><![CDATA[<div><blockquote>此渲染由 Yohaku API 生成，或存排版之虞，最佳体验请往：<a href="https://bambooo.top/posts/fiddle/p_status">https://bambooo.top/posts/fiddle/p_status</a></blockquote><div><h2 id="tldr">TLDR</h2><p>mx-space 支持博主点击头像右下角发布个人状态</p><p>官方功能描述如下：</p><p><a href="https://mx-space.js.org/docs/themes/shiro/extra#%E4%B8%AA%E4%BA%BA%E7%8A%B6%E6%80%81%E5%B1%95%E7%A4%BA">个人状态展示</a></p><p>但是按照官方提供方法配置完后，发布状态时肯定会失败，控制台报错大致是：</p><pre class="language-javascript lang-javascript"><code class="language-javascript lang-javascript">95900-30e790acc8113b00.js:1
   GET https://bambooo.top/api/v3/fn/shiro/status?lang=zh 404 (Not Found)
</code></pre><p>也就是找不到 status 这个函数</p><h2 id="">修复方法</h2><p>云函数复制我的粘贴过去保存</p><pre class="language-javascript lang-javascript"><code class="language-javascript lang-javascript">interface Status {
    emoji: string
    icon?: string
    desc?: string
    ttl: number
    untilAt: number
  }

  function assetAuth(ctx: Context) {
    const body = ctx.req.body
    const authKey = ctx.secret.key
    if (ctx.isAuthenticated) return
    if (body.key !== authKey) {
      throw new Error(&#x27;Unauthorized&#x27;)
    }
  }

  export default async function handler(ctx: Context) {
    const method = ctx.req.method.toLowerCase()

    switch (method) {
      case &#x27;get&#x27;: {
        return GET(ctx)
      }
      case &#x27;post&#x27;: {
        assetAuth(ctx)
        return POST(ctx)
      }
      case &#x27;delete&#x27;: {
        assetAuth(ctx)
        return DELETE(ctx)
      }
      case &#x27;options&#x27;: {
        return null
      }
      default: {
        throw new Error(&#x27;Method Not Allowed&#x27;)
      }
    }
  }

  const cacheKey = &#x27;shiro:status&#x27;

  async function DELETE(ctx: Context) {
    await ctx.storage.cache.del(cacheKey)
    ctx.broadcast(&#x27;shiro#status&#x27;, null)
  }

  async function POST(ctx: Context) {
    const body = ctx.req.body

    const { emoji, icon, desc } = body as Status
    const ttl = body.ttl || 86400

    const status = {
      emoji,
      icon,
      desc,
      ttl,
      untilAt: Date.now() + ttl * 1000,
    } as Status
    await ctx.storage.cache.set(cacheKey, JSON.stringify(status), ttl)
    ctx.broadcast(&#x27;shiro#status&#x27;, status)
    return null
  }

  async function GET(ctx: Context) {
    const status = await ctx.storage.cache.get(cacheKey)
    return status
  }
</code></pre></div><p style="text-align:right"><a href="https://bambooo.top/posts/fiddle/p_status#comments">览毕，何不一言？</a></p></div>]]></description><link>https://bambooo.top/posts/fiddle/p_status</link><guid isPermaLink="true">https://bambooo.top/posts/fiddle/p_status</guid><dc:creator><![CDATA[扳布]]></dc:creator><pubDate>Fri, 29 May 2026 06:08:31 GMT</pubDate></item><item><title><![CDATA[大模型上下文长度由什么决定]]></title><description><![CDATA[<div><blockquote>此渲染由 Yohaku API 生成，或存排版之虞，最佳体验请往：<a href="https://bambooo.top/posts/tech/cxt_len">https://bambooo.top/posts/tech/cxt_len</a></blockquote><div><p>大模型（LLM）支持的“上下文长度（Context Length / Context Window）”本质上是模型一次能够“看到”和“记住”的 token 数量。它由多个因素共同决定：</p><hr/><h1 id="1-transformer-">1. Transformer 架构本身</h1><p>绝大多数大模型基于 Transformer。</p><p>Transformer 的核心是 Attention：</p><p>\mathrm{Attention}(Q,K,V)=\mathrm{softmax}\left(\frac{QK^T}{\sqrt{d}}\right)V</p><p>这里的问题在于：</p><ul><li>每个 token 都要和之前所有 token 做 attention</li><li>序列越长，计算量越大</li><li>显存占用增长极快</li></ul><p>经典 Attention 的复杂度：</p><p>O(n^2)</p><p>其中：</p><ul><li>( n ) = 上下文 token 数量</li></ul><p>这意味着：</p><table><thead><tr><th> Context </th><th> Attention 规模 </th></tr></thead><tbody><tr><td> 2K      </td><td> 约 400 万      </td></tr><tr><td> 32K     </td><td> 约 10 亿       </td></tr><tr><td> 128K    </td><td> 约 160 亿      </td></tr></tbody></table><p>所以最根本的限制来自：</p><ul><li>Attention 计算量</li><li>Attention 显存消耗</li></ul><p>(<span>[Hugging Face][1]</span>)</p><hr/><h1 id="2-position-encoding">2. Position Encoding（位置编码）</h1><p>模型不仅要知道“词是什么”，还要知道“词在第几个位置”。</p><p>上下文长度很大程度取决于：</p><ul><li>Position Embedding 的设计</li><li>是否支持长距离位置泛化</li></ul><hr/><h2 id="">早期：绝对位置编码</h2><p>比如 GPT-2：</p><ul><li>训练时只支持 2048</li><li>超过直接崩</li></ul><p>因为位置 embedding 表是固定大小。</p><hr/><h2 id="rope">后来：RoPE（旋转位置编码）</h2><p>现在主流模型：</p><ul><li>Llama</li><li>Qwen</li><li>DeepSeek</li><li>Mistral</li></ul><p>基本都用 RoPE。</p><p>RoPE 可以更容易扩展上下文。(<span>[Reddit][2]</span>)</p><hr/><h2 id="-rope-">为什么 RoPE 还能继续扩？</h2><p>因为它不是：</p><p>“第 100 个 token 用第 100 个 embedding”</p><p>而是：</p><p>“通过旋转角度表达相对位置关系”。</p><p>所以：</p><ul><li>可以做插值（Position Interpolation）</li><li>可以做 RoPE Scaling</li><li>可以 NTK-aware scaling</li></ul><p>这也是为什么很多模型：</p><ul><li>训练时 8K</li><li>推理时能魔改到 32K / 128K</li></ul><hr/><h1 id="3-">3. 训练数据长度</h1><p>这是一个经常被忽视但很关键的问题。</p><p>模型不是“理论支持 128K”就真懂 128K。</p><p>如果训练时：</p><ul><li>大部分样本只有 2K</li><li>很少出现长文档</li></ul><p>那么：</p><p>即使技术上塞进去 128K，
模型也会：</p><ul><li>注意力退化</li><li>遗忘前文</li><li>中间内容丢失</li><li>出现 “Lost in the Middle”</li></ul><p>所以：</p><blockquote><p>真正有效的上下文长度 ≠ 宣称的上下文长度</p></blockquote>
<hr/><h1 id="4-kv-cache">4. KV Cache（最核心的工程瓶颈）</h1><p>推理时最重要的是 KV Cache。</p><p>Transformer 会缓存：</p><ul><li>Key</li><li>Value</li></ul><p>避免每次重新计算历史 token。(<span>[Hugging Face][1]</span>)</p><p>KV Cache 大小基本和：</p><ul><li>context length</li><li>layer 数</li><li>head 数</li><li>hidden size</li></ul><p>线性相关。</p><p>大致公式：</p><p>\mathrm{KV\ Cache}\propto L\times H\times D\times T</p><p>其中：</p><ul><li>(L)：层数</li><li>(H)：注意力头数</li><li>(D)：head dimension</li><li>(T)：context length</li></ul><hr/><h2 id="">为什么长上下文特别吃显存？</h2><p>因为：</p><p>上下文每增加一个 token：</p><ul><li>每一层都要多存 KV</li><li>所有历史 token 都要保留</li></ul><p>KV Cache 会线性爆炸。(<span>[LLMHardware.io][3]</span>)</p><p>例如：</p><table><thead><tr><th> 模型 </th><th> 4K Context </th><th> 128K Context </th></tr></thead><tbody><tr><td> 7B   </td><td> 几 GB      </td><td> 十几 GB      </td></tr><tr><td> 70B  </td><td> 十几 GB    </td><td> 几十~上百 GB </td></tr></tbody></table><p>所以很多时候：</p><blockquote><p>限制长上下文的不是模型，而是显存。</p></blockquote>
<hr/><h1 id="5-">5. 推理框架优化</h1><p>现代框架会做很多优化：</p><h2 id="flashattention">FlashAttention</h2><p>降低 Attention 显存占用。</p><h2 id="pagedattentionvllm">PagedAttention（vLLM）</h2><p>像虚拟内存一样管理 KV Cache。</p><h2 id="gqa--mqa">GQA / MQA</h2><p>减少 KV Head 数量。</p><p>比如：</p><ul><li>Multi-Head Attention：每个头都存 KV</li><li>Grouped Query Attention：多个 Query 共用 KV</li></ul><p>能显著降低长上下文成本。(<span>[InsiderLLM][4]</span>)</p><hr/><h1 id="6-">6. 模型训练方式</h1><p>长上下文模型通常会专门训练：</p><ul><li>Long context finetune</li><li>YaRN</li><li>NTK scaling</li><li>Position interpolation</li><li>LongLoRA</li></ul><p>否则模型虽然“能吃进去”，但理解效果会很差。</p><hr/><h1 id="7-">7. 理论上还有更深层限制</h1><p>目前 Transformer 长上下文仍有根本问题：</p><ul><li>信息稀释</li><li>注意力退化</li><li>中间遗忘</li><li>Memory bandwidth 瓶颈</li></ul><p>甚至学术界现在都在研究：</p><ul><li>KV Cache Compression</li><li>Sparse Attention</li><li>Linear Attention</li><li>State Space Model（Mamba）</li><li>RWKV</li></ul><p>因为传统 Transformer 长上下文成本太高。(<span>[arXiv][5]</span>)</p><hr/><h1 id="">简单总结</h1><p>大模型上下文长度主要由：</p><table><thead><tr><th> 因素              </th><th> 是否关键 </th></tr></thead><tbody><tr><td> Attention 复杂度  </td><td> 非常关键 </td></tr><tr><td> Position Encoding </td><td> 非常关键 </td></tr><tr><td> KV Cache 显存     </td><td> 极其关键 </td></tr><tr><td> 训练数据长度      </td><td> 非常关键 </td></tr><tr><td> GPU 显存/带宽     </td><td> 极其关键 </td></tr><tr><td> 推理优化          </td><td> 很关键   </td></tr><tr><td> 长上下文训练技巧  </td><td> 很关键   </td></tr></tbody></table><hr/><p>可以把它理解成：</p><blockquote><p>“上下文长度”本质上是：
模型结构 + 位置编码 + 显存容量 + KV Cache 管理 + 长文本训练能力
的综合结果。</p></blockquote>
<h1 id="">参考链接</h1><p><a href="https://huggingface.co/docs/transformers/main/cache_explanation?utm_source=chatgpt.com">&quot;Caching · Hugging Face&quot;</a></p><p><a href="https://www.reddit.com/r/LocalLLaMA/comments/16j8qa5?utm_source=chatgpt.com">I don&#x27;t understand context window extension&quot;</a></p><p><a href="https://llmhardware.io/guides/kv-cache-vram-context-length?utm_source=chatgpt.com">&quot;KV Cache and VRAM: How Context Length Blows Up Your GPU Memory (2026)&quot;</a></p><p>[&quot;KV Cache: Why Context Length Eats Your VRAM (And How to Fix It) | InsiderLLM&quot;](</p></div><p style="text-align:right"><a href="https://bambooo.top/posts/tech/cxt_len#comments">览毕，何不一言？</a></p></div>]]></description><link>https://bambooo.top/posts/tech/cxt_len</link><guid isPermaLink="true">https://bambooo.top/posts/tech/cxt_len</guid><dc:creator><![CDATA[扳布]]></dc:creator><pubDate>Wed, 27 May 2026 01:46:17 GMT</pubDate></item><item><title><![CDATA[RL - MDP]]></title><description><![CDATA[<link rel="preload" as="image" href="https://bambooo.top/api/v3/objects/image/47606023a198d5b65cd9a89211ed3b6e.webp"/><link rel="preload" as="image" href="https://bambooo.top/api/v3/objects/image/20260518162602401.webp"/><link rel="preload" as="image" href="https://bambooo.top/api/v3/objects/image/f3a64c4556bfd8784f47c95bad9b486d.webp"/><link rel="preload" as="image" href="https://bambooo.top/api/v3/objects/image/c835cc992132428612dbd7a988033736.webp"/><link rel="preload" as="image" href="https://bambooo.top/api/v3/objects/image/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20260519140932_12_11.png"/><div><blockquote>此渲染由 Yohaku API 生成，或存排版之虞，最佳体验请往：<a href="https://bambooo.top/posts/tech/RL-MDP">https://bambooo.top/posts/tech/RL-MDP</a></blockquote><div><p>强化学习已经在各个领域大放光彩，游戏、机器人，在LLM领域更是后训练的强力手段。所以，有必要添加进个人技能库。</p><p><img alt="强化学习发展" src="https://bambooo.top/api/v3/objects/image/47606023a198d5b65cd9a89211ed3b6e.webp"/></p><p>上图展示了RL的发展时间线，早在上世纪五六十年代，其理论就已经出现，不得不说，真是天才。不过真正让强化学习进入大众视野的应该是在2016年3月，DeepMind开发的AlphaGo程序利用强化学习算法以4:1击败世界围棋高手李世石（那会我还在上初中🥹）。</p><h2 id="">前言</h2><p>强化学习涉及到几个重要概念，Agent、Action、State、Reward、Environment。</p><p><img alt="强化学习原理" src="https://bambooo.top/api/v3/objects/image/20260518162602401.webp"/></p><p>上图解释了强化学习的基本原理。Agent在完成某项任务时，首先通过动作Action与周围环境Environment进行交互，在两者作用下，智能体会产生新的状态（State），同时环境会给出一个立即回报（Reward），如此循环下去，Agent与环境进行不断交互从而产生很多数据。强化学习算法利用产生的数据修改自身的动作策略，再与环境交互，产生新的数据，并利用新的数据进一步改善自身行为，经过数次迭代学习后，Agent最终能学习到完成相应任务的最优动作（策略）。</p><p>从原理可以看出RL与其他机器学习算法比如监督学习和非监督学习的一些区别。在SFT和UL中，数据是静态的，不需要和环境交互，比如图像识别，只要给足够差异样本，将数据输入到深度神经网络中进行训练即可。而强化学习的过程是动态的，不断交互的过程，所需要的数据也是通过与环境不断交互产生。所以，与SFT和UL相比，RL涉及到的对象更多，比如动作、环境、状态转移概率和奖励函数等。RL更像是人类学习的过程。另外，深度学习如图像识别和语音识别解决的是感知的问题，强化学习解决的是决策的问题。人工智能的终极目的是通过感知进行智能决策。所以，将近年发展起来的深度学习技术与强化学习算法结合而产生的深度强化学习算法是人类实现人工智能终极目的的一个很有前景的方法。</p><p>一些学者探索出一套可以解决大部分强化学习问题的框架 —— 马尔科夫决策过程，简称MDP。</p><h2 id="">马尔科夫过程</h2><h3 id="">随机过程</h3><p><strong>随机过程（stochastic process）</strong>是概率论的“动力学”部分。概率论的研究对象是静态的随机现象，而随机过程的研究对象是随时间演变的随机现象（比如天气随时间的变化、城市交通随时间的变化）。在随机过程中，随机现象在某时刻 $t$ 的取值是一个随机变量，用 $S<em>t$ 表示，所有可能的状态组成状态集合 $S$。随机现象便是状态的变化过程。在某时刻 $t$ 的状态 $S</em>t$ 通常取决于 $t$ 时刻之前的状态。我们将已知历史信息 $(S<em>1, ..., S</em>t)$ 时下一个时刻状态 $S<em>{t+1}$ 的概率表示成 $P(S</em>{t+1} \mid (S<em>1, ..., S</em>t))$。</p><h3 id="">马尔科夫性质</h3><p>当且仅当某时刻的状态只取决于上一时刻的状态时，一个随机过程被称为具有<strong>马尔可夫性质</strong>（Markov property），用公式表示为 $P(S<em>{t+1} \mid S</em>t) = P(S<em>{t+1} \mid S</em>1, ..., S_t)$。也就是说，当前状态是未来的充分统计量，即下一个状态只取决于当前状态，而不会受到过去状态的影响。需要明确的是，具有马尔可夫性并不代表这个随机过程就和历史完全没有关系。因为虽然 $t+1$ 时刻的状态只与 $t$ 时刻的状态有关，但是 $t$ 时刻的状态其实包含了 $t-1$ 时刻的状态的信息，通过这种链式的关系，历史的信息被传递到了现在。马尔可夫性可以大大简化运算，因为只要当前状态可知，所有的历史信息都不再需要了，利用当前状态信息就可以决定未来。</p><h3 id="markov-process">马尔科夫过程（Markov process）</h3><p><strong>马尔可夫过程</strong>指具有马尔可夫性质的随机过程，也被称为 <strong>马尔可夫链</strong>（Markov chain）。我们通常用元组 $\langle S, P \rangle$ 描述一个马尔可夫过程，其中 $S$ 是有限数量的状态集合，$P$ 是状态转移矩阵（state transition matrix）。假设一共有 $n$ 个状态，此时 $S = {s<em>1, s</em>2, \dots, s_n}$。状态转移矩阵 $P$ 定义了所有状态对之间的转移概率，即</p><p>$$
P = 
\begin{bmatrix}
P(s<em>1 \mid s</em>1) &amp; \cdots &amp; P(s<em>n \mid s</em>1) \
\vdots &amp; \ddots &amp; \vdots \
P(s<em>1 \mid s</em>n) &amp; \cdots &amp; P(s<em>n \mid s</em>n)
\end{bmatrix}
$$</p><p>矩阵 $P$ 中第 $i$ 行第 $j$ 列元素 $P(s<em>j \mid s</em>i) = P(S<em>{t+1} = s</em>j \mid S<em>t = s</em>i)$ 表示从状态 $s<em>i$ 转移到状态 $s</em>j$ 的概率，我们称 $P(s&#x27; \mid s)$ 为状态转移函数。从某个状态出发，到达其他状态的概率和必须为 1，即状态转移矩阵 $P$ 的每一行的和为 1。</p><p>下图是一个具有 6 个状态的马尔可夫过程的简单例子。其中每个绿色圆圈表示一个状态，每个状态都有一定概率（包括概率为 0）转移到其他状态，其中 $s<em>6$ 通常被称为 <strong>终止状态</strong>（terminal state），因为它不会再转移到其他状态，可以理解为它永远以概率 1 转移到自己。状态之间的虚线箭头表示状态的转移，箭头旁的数字表示该状态转移发生的概率。从每个状态出发转移到其他状态的概率总和为 1。例如，$s</em>1$ 有 90% 概率保持不变，有 10% 概率转移到 $s<em>2$，而在 $s</em>2$ 又有 50% 概率回到 $s<em>1$，有 50% 概率转移到 $s</em>3$。</p><p><img src="https://bambooo.top/api/v3/objects/image/f3a64c4556bfd8784f47c95bad9b486d.webp"/></p><p>我们可以写出这个马尔可夫过程的状态转移矩阵：</p><p>$$
P = 
\begin{bmatrix}
0.9 &amp; 0.1 &amp; 0 &amp; 0 &amp; 0 &amp; 0 \
0.5 &amp; 0 &amp; 0.5 &amp; 0 &amp; 0 &amp; 0 \
0 &amp; 0 &amp; 0 &amp; 0.6 &amp; 0 &amp; 0.4 \
0 &amp; 0 &amp; 0 &amp; 0 &amp; 0.3 &amp; 0.7 \
0 &amp; 0.2 &amp; 0.3 &amp; 0.5 &amp; 0 &amp; 0 \
0 &amp; 0 &amp; 0 &amp; 0 &amp; 0 &amp; 1
\end{bmatrix}
$$</p><p>其中第 $i$ 行第 $j$ 列的值 $P<em>{i,j}$ 则代表从状态 $s</em>i$ 转移到 $s_j$ 的概率。</p><p>给定一个马尔可夫过程，我们就可以从某个状态出发，根据它的状态转移矩阵生成一个状态序列（episode），这个步骤也被叫做采样（sampling）。例如，从 $s<em>1$ 出发，可以生成序列 $s</em>1 \to s<em>2 \to s</em>3 \to s<em>6$ 或序列 $s</em>1 \to s<em>1 \to s</em>2 \to s<em>3 \to s</em>4 \to s<em>5 \to s</em>3 \to s_6$ 等。生成这些序列的概率和状态转移矩阵有关。</p><h2 id="">马尔科夫奖励过程</h2><p>在马尔可夫过程的基础上加入奖励函数 $r$ 和折扣因子 $\gamma$，就可以得到 <strong>马尔可夫奖励过程</strong>（Markov reward process）。一个马尔可夫奖励过程由 $(S, P, r, \gamma)$ 构成，各个组成元素的含义如下所示。</p><ul><li>$S$ 是有限状态的集合。</li><li>$P$ 是状态转移矩阵。</li><li>$r$ 是奖励函数，某个状态 $s$ 的奖励 $r(s)$ 指转移到该状态时可以获得奖励的期望。</li><li>$\gamma$ 是折扣因子（discount factor），$\gamma$ 的取值范围为 $[0, 1)$。引入折扣因子的理由为远期利益具有一定不确定性，有时我们更希望能够尽快获得一些奖励，所以我们需要对远期利益打一些折扣。接近 $1$ 的 $\gamma$ 更关注长期的累计奖励，接近 $0$ 的 $\gamma$ 更考虑短期奖励。</li></ul><h3 id="">回报</h3><p>在一个马尔可夫奖励过程中，从第 $t$ 时刻状态 $S<em>t$ 开始，直到终止状态时，所有奖励的衰减之和称为 <strong>回报</strong> $G</em>t$（Return），公式如下：</p><p>$$
G<em>t = R</em>t + \gamma R<em>{t+1} + \gamma^2 R</em>{t+2} + \cdots = \sum<em>{k=0}^\infty \gamma^k R</em>{t+k}
$$</p><p>其中，$R<em>t$ 表示在时刻 $t$ 获得的奖励。在下图中，继续沿用马尔可夫过程的例子，并在其基础上添加奖励函数，构建成一个马尔可夫奖励过程。例如，进入状态 $s</em>2$ 可以得到奖励 $-2$，表明我们不希望进入 $s<em>2$，进入 $s</em>4$ 可以获得最高的奖励 $10$，但是进入 $s_6$ 之后奖励为零，并且此时序列也终止了。</p><p><img src="https://bambooo.top/api/v3/objects/image/c835cc992132428612dbd7a988033736.webp"/></p><p>比如选取 $s<em>1$ 为起始状态，设置 $\gamma = 0.5$，采样到一条状态序列为 $s</em>1 \to s<em>2 \to s</em>3 \to s<em>6$，就可以计算 $s</em>1$ 的回报 $G_1$，得到</p><p>$$
G_1 = -1 + 0.5 \times (-2) + 0.5^2 \times (-2) = -2.5
$$</p><h3 id="">价值函数</h3><p>在马尔可夫奖励过程中，一个状态的期望回报（即从这个状态出发的未来累积奖励的期望）被称为这个状态的价值（value）。所有状态的价值就组成了价值函数（value function），价值函数的输入为某个状态，输出为这个状态的价值。我们将价值函数写成 $V(s) = \mathbb{E}[G<em>t \mid S</em>t = s]$，展开为</p><p>$$
\begin{aligned}
V(s) &amp;= \mathbb{E}[G<em>t \mid S</em>t = s] \
&amp;= \mathbb{E}[R<em>t + \gamma R</em>{t+1} + \gamma^2 R<em>{t+2} + \dots \mid S</em>t = s] \
&amp;= \mathbb{E}[R<em>t + \gamma (R</em>{t+1} + \gamma R<em>{t+2} + \dots) \mid S</em>t = s] \
&amp;= \mathbb{E}[R<em>t + \gamma G</em>{t+1} \mid S<em>t = s] \
&amp;= \mathbb{E}[R</em>t + \gamma V(S<em>{t+1}) \mid S</em>t = s]
\end{aligned}
$$</p><p>在上式的最后一个等号中，一方面，即时奖励的期望正是奖励函数的输出，即 $\mathbb{E}[R<em>t \mid S</em>t = s] = r(s)$；另一方面，等式中剩余部分 $\mathbb{E}[\gamma V(S<em>{t+1}) \mid S</em>t = s]$ 可以根据从状态 $s$ 出发的转移概率得到，即可以得到</p><p>$$
V(s) = r(s) + \gamma \sum<em>{s&#x27; \in S} p(s&#x27; \mid s) V(s&#x27;)
$$
上式就是马尔可夫奖励过程中非常有名的 <strong>贝尔曼方程</strong>（Bellman equation），对每一个状态都成立。<br/>若一个马尔可夫奖励过程一共有 $n$ 个状态，即 $S = {s</em>1, s<em>2, \dots, s</em>n}$，我们将所有状态的价值表示成一个列向量 $V = [V(s<em>1), V(s</em>2), \dots, V(s<em>n)]^T$，同理，将奖励函数写成一个列向量<br/>$R = [r(s</em>1), r(s<em>2), \dots, r(s</em>n)]^T$。于是我们可以将贝尔曼方程写成矩阵的形式：</p><p>$$
V = R + \gamma PV
$$</p><p>$$
\begin{bmatrix}
V(s<em>1) \
V(s</em>2) \
\vdots \
V(s<em>n)
\end{bmatrix}
=
\begin{bmatrix}
r(s</em>1) \
r(s<em>2) \
\vdots \
r(s</em>n)
\end{bmatrix}
+
\gamma
\begin{bmatrix}
P(s<em>1 \mid s</em>1) &amp; P(s<em>1 \mid s</em>2) &amp; \cdots &amp; P(s<em>1 \mid s</em>n) \
P(s<em>2 \mid s</em>1) &amp; P(s<em>2 \mid s</em>2) &amp; \cdots &amp; P(s<em>2 \mid s</em>n) \
\vdots &amp; \vdots &amp; \ddots &amp; \vdots \
P(s<em>n \mid s</em>1) &amp; P(s<em>n \mid s</em>2) &amp; \cdots &amp; P(s<em>n \mid s</em>n)
\end{bmatrix}
\begin{bmatrix}
V(s<em>1) \
V(s</em>2) \
\vdots \
V(s_n)
\end{bmatrix}
$$</p><p>我们可以直接根据矩阵运算求解，得到以下解析解：</p><p>$$
V = R + \gamma PV
$$</p><p>$$
(I - \gamma P)V = R
$$</p><p>$$
V = (I - \gamma P)^{-1}R
$$</p><h2 id="">马尔科夫决策过程</h2><p>前面讨论到的马尔可夫过程和马尔可夫奖励过程都是自发改变的随机过程；而如果有一个外界的“刺激”来共同改变这个随机过程，就有了 <strong>马尔可夫决策过程</strong>（Markov decision process, MDP）。我们将这个来自外界的刺激称为 <strong>智能体</strong>（agent）的动作，在马尔可夫奖励过程（MRP）的基础上加入动作，就得到了马尔可夫决策过程（MDP）。马尔可夫决策过程由元组 $\langle S, A, P, r, \gamma \rangle$ 构成，其中：</p><ul><li>$S$ 是状态的集合；</li><li>$A$ 是动作的集合；</li><li>$\gamma$ 是折扣因子；</li><li>$r(s, a)$ 是奖励函数，此时奖励可以同时取决于状态 $s$ 和动作 $a$，在奖励函数只取决于状态 $s$ 时，则退化为 $r(s)$；</li><li>$P(s&#x27; \mid s, a)$ 是状态转移函数，表示在状态 $s$ 执行动作 $a$ 之后到达状态 $s&#x27;$ 的概率。</li></ul><p>马尔科夫决策过程是一个与时间相关的不断进行的过程，在智能体与环境 MDP 之间存在一个不断交互的过程。一般而言，智能体根据当前状态 $S<em>t$ 选择动作 $A</em>t$；对于状态 $S<em>t$ 和动作 $A</em>t$，MDP 根据奖励函数和状态转移函数得到  $S<em>{t+1}$ 和 $R</em>t$ 并反馈给智能体，智能体的目标是最嗲话得到的累计奖励。智能体根据当前状态从动作的集合 $A$ 中选择一个动作的函数，被称为策略。</p><p><img alt="马尔科夫决策过程" src="https://bambooo.top/api/v3/objects/image/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20260519140932_12_11.png"/></p><h3 id="">策略</h3><p>智能体的策略（Policy）通常用字母 $\pi$ 表示。策略 $\pi(a \mid s) = P(A<em>t = a \mid S</em>t = s)$ 是一个函数，表示在输入状态 $s$ 情况下采取动作 $a$ 的概率。当一个策略是确定性策略（deterministic policy）时，它在每个状态时只输出一个确定性的动作，即只有该动作的概率为 $1$，其他动作的概率为 $0$；当一个策略是随机性策略（stochastic policy）时，它在每个状态时输出的关于动作的概率分布，然后根据该分布进行采样就可以得到一个动作。在MDP中，由于马尔可夫性质的存在，策略只需要与当前状态有关，不需要考虑历史状态。回顾一下在MRP中的价值函数，在MDP中也同样可以定义类似的价值函数。但此时的价值函数与策略有关，这意为着对于两个不同的策略来说，它们在同一个状态下的价值也很可能是不同的。这很好理解，因为不同的策略会采取不同的动作，从而之后会遇到不同的状态，以及获得不同的奖励，所以它们的累积奖励的期望也就不同，即状态价值不同。</p><h3 id="">状态价值函数</h3><p>我们用 $V^\pi(s)$ 表示在 MDP 中基于策略 $\pi$ 的状态价值函数（state-value function），定义为从状态 $s$ 出发遵循策略 $\pi$ 能获得的期望回报，数学表达为：</p><p>$$
V^\pi(s) = \mathbb{E}<em>{\pi}[G</em>t \mid S_t = s]
$$</p><h3 id="">动作价值函数</h3><p>不同于 MRP，在 MDP 中，由于动作的存在，我们额外定义一个 <strong>动作价值函数</strong>（action-value function）。我们用 $Q^\pi(s, a)$ 表示在 MDP 遵循策略 $\pi$ 时，对当前状态 $s$ 执行动作 $a$ 得到的期望回报：</p><p>$$
Q^\pi(s, a) = \mathbb{E}<em>{\pi}[G</em>t \mid S<em>t = s, A</em>t = a]
$$</p><p>状态价值函数和动作价值函数之间的关系：在使用策略 $\pi$ 中，状态 $s$ 的价值等于在该状态下基于策略 $\pi$ 采取所有动作的概率与相应的价值相乘再求和的结果：</p><p>$$
V^\pi(s) = \sum_{a \in A} \pi(a \mid s) Q^\pi(s, a)
$$</p><p>使用策略 $\pi$ 时，状态 $s$ 下采取动作 $a$ 的价值等于即时奖励加上经过衰减后的所有可能的下一个状态的状态转移概率与相应的价值的乘积：</p>
<p>$$
Q^\pi(s, a) = r(s, a) + \gamma \sum_{s&#x27; \in S} P(s&#x27; \mid s, a) V^\pi(s&#x27;)
$$</p></div><p style="text-align:right"><a href="https://bambooo.top/posts/tech/RL-MDP#comments">览毕，何不一言？</a></p></div>]]></description><link>https://bambooo.top/posts/tech/RL-MDP</link><guid isPermaLink="true">https://bambooo.top/posts/tech/RL-MDP</guid><dc:creator><![CDATA[扳布]]></dc:creator><pubDate>Mon, 18 May 2026 08:31:39 GMT</pubDate></item><item><title><![CDATA[KL散度]]></title><description><![CDATA[<div><blockquote>此渲染由 Yohaku API 生成，或存排版之虞，最佳体验请往：<a href="https://bambooo.top/posts/learn/KL">https://bambooo.top/posts/learn/KL</a></blockquote><div><blockquote>
<p>这篇文章目的是方便更通俗、更简单的理解KL散度，所以会尽可能减少公式推导，更多的阐述KL的理论知识和应用。</p></blockquote>
<h2 id="">介绍</h2><p>KL散度（Kullback–Leibler Divergence），通常简称 <strong>KL Divergence</strong>，中文叫：</p><ul><li>相对熵</li><li>KL距离（严格来说它不是“距离”）</li><li>K-L散度</li></ul><p>它是信息论中的核心概念之一，用来衡量：</p><blockquote><p>“一个概率分布，相对于另一个概率分布，损失了多少信息。”</p></blockquote>
<p>或者更直观一点：</p><blockquote><p>“如果真实世界遵循分布 P，但你错误地使用了分布 Q 去描述它，会多付出多少代价。”</p></blockquote>
<h3 id="">直观理解</h3><p>假设：</p><ul><li>P：真实分布（真实世界）</li><li>Q：你的模型认为的分布（预测世界）</li></ul><p>KL散度衡量：</p><blockquote><p>“Q 对 P 的描述到底有多差。”</p></blockquote>
<hr/><p><strong>一个简单例子：</strong></p><p>假设真实分布 P：</p><table><thead><tr><th> 事件 </th><th> 概率 </th></tr></thead><tbody><tr><td> 猫   </td><td> 0.9  </td></tr><tr><td> 狗   </td><td> 0.1  </td></tr></tbody></table><p>而你的模型 Q：</p><table><thead><tr><th> 事件 </th><th> 概率 </th></tr></thead><tbody><tr><td> 猫   </td><td> 0.5  </td></tr><tr><td> 狗   </td><td> 0.5  </td></tr></tbody></table><p>那么：</p><ul><li>真实世界里几乎总是“猫”</li><li>但你的模型认为“猫狗一样多”</li></ul><p>于是：</p><ul><li>你的预测浪费了大量信息</li><li>编码效率下降</li><li>模型认知错误</li></ul><p>KL散度会很大。</p><hr/><p>如果模型 Q：</p><table><thead><tr><th> 事件 </th><th> 概率 </th></tr></thead><tbody><tr><td> 猫   </td><td> 0.89 </td></tr><tr><td> 狗   </td><td> 0.11 </td></tr></tbody></table><p>那么：</p><ul><li>Q 很接近 P</li><li>KL散度就很小</li></ul><h2 id="kl">KL散度的本质：信息损失</h2><p>KL散度来自信息论。</p><p>信息论里有一个核心思想：</p><blockquote><p>概率越小，信息量越大。</p></blockquote>
<p>比如：</p><ul><li>“太阳明天升起” → 信息量很小</li><li>“明天外星人降临” → 信息量巨大</li></ul><p>因为后者概率低。</p><hr/><p>KL散度本质上衡量：</p><blockquote><p>“你因为使用错误概率模型，而额外浪费的信息量。”</p></blockquote>
<p>所以：</p><ul><li>KL越小 → 模型越接近真实分布</li><li>KL越大 → 模型偏差越严重</li></ul><h2 id="kl">KL散度的重要性质</h2><h3 id="1-kl--0">1. KL ≥ 0</h3><p>KL散度永远非负：</p><p>DKL(P∣∣Q)≥0D_{KL}(P || Q) \ge 0DKL(P∣∣Q)≥0</p><p>只有：</p><p>P=QP = QP=Q</p><p>时才等于 0。</p><p>即：</p><blockquote><p>完全一致时，没有信息损失。</p></blockquote>
<hr/><h3 id="2-kl">2. KL不是对称的</h3><p>这是最重要性质之一。</p><p>一般：</p><p>DKL(P∣∣Q)≠DKL(Q∣∣P)D<em>{KL}(P || Q) \neq D</em>{KL}(Q || P)DKL(P∣∣Q)\=DKL(Q∣∣P)</p><p>即：</p><ul><li>“Q 用来近似 P”</li><li>和</li><li>“P 用来近似 Q”</li></ul><p>完全不是一回事。</p><hr/><h3 id="">为什么不对称很重要？</h3><p>因为：</p><h4 id="1">场景1：覆盖真实分布</h4><p>如果真实分布有多个峰：</p><ul><li>你漏掉一个峰</li><li>KL惩罚会非常大</li></ul><p>这叫：</p><blockquote><p>mode covering（模式覆盖）</p></blockquote>
<hr/><h4 id="2">场景2：只抓最主要模式</h4><p>有些任务更关注：</p><ul><li>最重要概率区域</li></ul><p>而不是覆盖所有区域。</p><p>于是另一方向KL会有不同效果。</p><hr/><p>这直接影响：</p><ul><li>VAE</li><li>GAN</li><li>扩散模型</li><li>强化学习</li><li>RLHF</li></ul><p>中的训练行为。</p><h2 id="kl">KL散度的应用</h2><h3 id="1-">1. 分类任务</h3><p>例如：</p><ul><li>图像分类</li><li>NLP分类</li><li>LLM next-token预测</li></ul><p>模型输出：</p><pre class=""><code class="">softmax概率分布
</code></pre>
<p>真实标签：</p><pre class=""><code class="">one-hot分布
</code></pre>
<p>训练目标：</p><blockquote><p>让预测分布接近真实分布</p></blockquote>
<p>于是使用：</p><ul><li>Cross Entropy</li><li>本质就是KL优化</li></ul><hr/><p><strong>举例</strong></p><p>真实：</p><pre class=""><code class="">猫 = 1
狗 = 0
</code></pre>
<p>模型：</p><pre class=""><code class="">猫 = 0.7
狗 = 0.3
</code></pre>
<p>训练会推动：</p><pre class=""><code class="">猫 -&gt; 1
狗 -&gt; 0
</code></pre>
<p>即：</p><p>例如：</p><ul><li>图像分类</li><li>NLP分类</li><li>LLM next-token预测</li></ul><p>模型输出：</p><pre class=""><code class="">softmax概率分布
</code></pre>
<p>真实标签：</p><pre class=""><code class="">one-hot分布
</code></pre>
<p>训练目标：</p><blockquote><p>让预测分布接近真实分布</p></blockquote>
<p>于是使用：</p><ul><li>Cross Entropy</li><li>本质就是KL优化</li></ul><hr/><p><strong>举例</strong></p><p>真实：</p><pre class=""><code class="">猫 = 1
狗 = 0
</code></pre>
<p>模型：</p><pre class=""><code class="">猫 = 0.7
狗 = 0.3
</code></pre>
<p>训练会推动：</p><pre class=""><code class="">猫 -&gt; 1
狗 -&gt; 0
</code></pre>
<p>即：</p><p>$Q \to P$</p><h3 id="2-llm">2. LLM</h3><p>LLM本质是：</p><blockquote><p>下一个token概率预测器。</p></blockquote>
<p>例如：</p><p>输入：</p><pre class=""><code class="">今天天气真
</code></pre>
<p>模型预测：</p><table><thead><tr><th> Token </th><th> 概率 </th></tr></thead><tbody><tr><td> 好    </td><td> 0.8  </td></tr><tr><td> 差    </td><td> 0.1  </td></tr><tr><td> 热    </td><td> 0.1  </td></tr></tbody></table><p>真实token：</p><pre class=""><code class="">好
</code></pre>
<p>训练目标：</p><ul><li>最大化正确token概率</li><li>等价于最小化KL散度</li></ul><h3 id="3-distillation">3. 知识蒸馏（Distillation）</h3><p>KL散度经典应用。</p><h4 id="teacher--student">Teacher → Student</h4><p>大模型：</p><pre class=""><code class="">Teacher
</code></pre>
<p>小模型：</p><pre class=""><code class="">Student
</code></pre>
<p>Teacher输出：</p><pre class=""><code class="">猫:0.7
狗:0.2
狐狸:0.1
</code></pre>
<p>Student学习：</p><pre class=""><code class="">尽量模仿Teacher概率分布
</code></pre>
<p>这里不能只学：</p><pre class=""><code class="">正确类别
</code></pre>
<p>而是要学习：</p><blockquote><p>整个概率结构。</p></blockquote>
<p>因此使用：</p><p>$D_{KL}(Teacher || Student)$</p><hr/><h4 id="">为什么有效？</h4><p>因为Teacher包含：</p><ul><li>类别相似性</li><li>不确定性</li><li>结构知识</li></ul><p>例如：</p><pre class=""><code class="">猫 和 狐狸 有点像
</code></pre>
<p>这些信息：</p><p>one-hot标签无法表达。</p><h3 id="4-rl">4. RL</h3><h4 id="ppokl">PPO中的KL约束</h4><p>PPO不允许策略突然变化太大。</p><p>否则：</p><ul><li>训练崩溃</li><li>reward hacking</li><li>模型退化</li></ul><p>因此：</p><p>新策略必须接近旧策略：</p><p>DKL(πnew∣∣πold)D<em>{KL}(\pi</em>{new} || \pi_{old})DKL(πnew∣∣πold)</p><hr/><h4 id="">本质</h4><p>KL在这里扮演：</p><blockquote><p>“刹车系统”</p></blockquote>
<p>避免：</p><ul><li>policy collapse</li><li>梯度爆炸</li><li>行为突变</li></ul><h3 id="5-rlhf">5. RLHF</h3><p>RLHF中：</p><ul><li>有一个Reference Model</li><li>一个正在训练的Policy Model</li></ul><p>训练时：</p><p>不能让模型偏离原模型太远。</p><p>因此加入：</p><p>KL penalty。</p><hr/><h4 id="">为什么？</h4><p>否则模型可能：</p><p>为了奖励：</p><ul><li>胡说八道</li><li>极端输出</li><li>reward hacking</li></ul><p>KL约束：</p><blockquote><p>保持语言能力稳定。</p></blockquote>
<hr/><h4 id="rlhf">RLHF目标本质</h4><p>不是：</p><pre class=""><code class="">最大化reward
</code></pre>
<p>而是：</p><pre class=""><code class="">在不偏离原模型太远情况下提高reward
</code></pre>
<p>因此：</p><p>KL是核心稳定器。</p><h2 id="">公式</h2><h3 id="kl">一、离散分布的KL散度</h3><p>对于两个离散概率分布：</p><ul><li>$P(x)$：真实分布</li><li>$Q(x)$：近似分布</li></ul><p>KL散度定义为：</p><p>$D<em>{KL}(P\parallel Q)=\sum</em>x P(x)\log\frac{P(x)}{Q(x)}$</p><hr/><h3 id="">含义解释</h3><p>公式可以拆成三部分理解：</p><h4 id="1-px">1. $P(x)$</h4><p>表示：</p><pre class=""><code class="">真实世界中事件x出现的概率
</code></pre>
<hr/><h4 id="2-fracpxqx">2. $\frac{P(x)}{Q(x)}$</h4><p>表示：</p><pre class=""><code class="">真实概率 和 模型概率 的比值
</code></pre>
<p>如果：</p><ul><li>Q低估了真实概率</li><li>比值会很大</li><li>惩罚会增加</li></ul><hr/><h4 id="3-log-fracpxqx">3. $\log \frac{P(x)}{Q(x)}$</h4><p>表示：</p><pre class=""><code class="">信息量差异
</code></pre>
<p>因为信息论里：</p><ul><li>log体现编码长度</li><li>信息增益</li><li>熵变化</li></ul><hr/><h4 id="4-">4. 最外层求和</h4><p>表示：</p><pre class=""><code class="">对所有可能事件做期望
</code></pre>
<p>即：</p><blockquote><p>平均信息损失。</p></blockquote>
<hr/><h3 id="">二、连续分布形式</h3><p>如果是连续概率分布：</p><p>例如：</p><ul><li>高斯分布</li><li>连续latent space</li></ul><p>则KL散度写成积分：</p><p>$D_{KL}(P\parallel Q)=\int P(x)\log\frac{P(x)}{Q(x)},dx$</p></div><p style="text-align:right"><a href="https://bambooo.top/posts/learn/KL#comments">览毕，何不一言？</a></p></div>]]></description><link>https://bambooo.top/posts/learn/KL</link><guid isPermaLink="true">https://bambooo.top/posts/learn/KL</guid><dc:creator><![CDATA[扳布]]></dc:creator><pubDate>Mon, 18 May 2026 02:28:47 GMT</pubDate></item><item><title><![CDATA[五一南京游]]></title><description><![CDATA[<link rel="preload" as="image" href="https://bambooo.top/api/v3/objects/image/20260505130125090.webp"/><link rel="preload" as="image" href="https://bambooo.top/api/v3/objects/image/20260505130125089.webp"/><link rel="preload" as="image" href="https://bambooo.top/api/v3/objects/image/20260505130125088.webp"/><link rel="preload" as="image" href="https://bambooo.top/api/v3/objects/image/20260505130125087.webp"/><link rel="preload" as="image" href="https://bambooo.top/api/v3/objects/image/20260505130125086.webp"/><link rel="preload" as="image" href="https://bambooo.top/api/v3/objects/image/20260505130125085.webp"/><link rel="preload" as="image" href="https://bambooo.top/api/v3/objects/image/20260505130125084.webp"/><link rel="preload" as="image" href="https://bambooo.top/api/v3/objects/image/20260505130125083.webp"/><link rel="preload" as="image" href="https://bambooo.top/api/v3/objects/image/20260505130125082.webp"/><link rel="preload" as="image" href="https://bambooo.top/api/v3/objects/image/20260505130125081.webp"/><link rel="preload" as="image" href="https://bambooo.top/api/v3/objects/image/20260505130125080.webp"/><link rel="preload" as="image" href="https://bambooo.top/api/v3/objects/image/20260505130125079.webp"/><link rel="preload" as="image" href="https://bambooo.top/api/v3/objects/image/20260505130125078.webp"/><link rel="preload" as="image" href="https://bambooo.top/api/v3/objects/image/20260505130125077.webp"/><link rel="preload" as="image" href="https://bambooo.top/api/v3/objects/image/20260505130125076.webp"/><link rel="preload" as="image" href="https://bambooo.top/api/v3/objects/image/20260505130125075.webp"/><link rel="preload" as="image" href="https://bambooo.top/api/v3/objects/image/20260505130125074.webp"/><link rel="preload" as="image" href="https://bambooo.top/api/v3/objects/image/20260505130125073.webp"/><link rel="preload" as="image" href="https://bambooo.top/api/v3/objects/image/20260505130125072.webp"/><link rel="preload" as="image" href="https://bambooo.top/api/v3/objects/image/20260505130125071.webp"/><link rel="preload" as="image" href="https://bambooo.top/api/v3/objects/image/20260505130125070.webp"/><link rel="preload" as="image" href="https://bambooo.top/api/v3/objects/image/20260505130125069.webp"/><link rel="preload" as="image" href="https://bambooo.top/api/v3/objects/image/20260505130125068.webp"/><link rel="preload" as="image" href="https://bambooo.top/api/v3/objects/image/20260505130125067.webp"/><link rel="preload" as="image" href="https://bambooo.top/api/v3/objects/image/20260505130125066.webp"/><link rel="preload" as="image" href="https://bambooo.top/api/v3/objects/image/20260505130125065.webp"/><link rel="preload" as="image" href="https://bambooo.top/api/v3/objects/image/20260505130125064.webp"/><link rel="preload" as="image" href="https://bambooo.top/api/v3/objects/image/20260505130125063.webp"/><link rel="preload" as="image" href="https://bambooo.top/api/v3/objects/image/20260505130125062.webp"/><link rel="preload" as="image" href="https://bambooo.top/api/v3/objects/image/20260505130125061.webp"/><link rel="preload" as="image" href="https://bambooo.top/api/v3/objects/image/20260505130125060.webp"/><link rel="preload" as="image" href="https://bambooo.top/api/v3/objects/image/20260505130125059.webp"/><link rel="preload" as="image" href="https://bambooo.top/api/v3/objects/image/20260505130125058.webp"/><link rel="preload" as="image" href="https://bambooo.top/api/v3/objects/image/20260505130125057.webp"/><link rel="preload" as="image" href="https://bambooo.top/api/v3/objects/image/20260505130125056.webp"/><link rel="preload" as="image" href="https://bambooo.top/api/v3/objects/image/20260505130125055.webp"/><link rel="preload" as="image" href="https://bambooo.top/api/v3/objects/image/20260505130125054.webp"/><link rel="preload" as="image" href="https://bambooo.top/api/v3/objects/image/20260505130125053.webp"/><div><blockquote>此渲染由 Yohaku API 生成，或存排版之虞，最佳体验请往：<a href="https://bambooo.top/notes/4">https://bambooo.top/notes/4</a></blockquote><div><h2 id="">梧桐大道</h2><p><img height="1440" src="https://bambooo.top/api/v3/objects/image/20260505130125090.webp" width="1920"/></p><h2 id="">鸡鸣寺</h2><p><img src="https://bambooo.top/api/v3/objects/image/20260505130125089.webp"/>
<img src="https://bambooo.top/api/v3/objects/image/20260505130125088.webp"/>
<img src="https://bambooo.top/api/v3/objects/image/20260505130125087.webp"/></p><h2 id="">红山森林动物园</h2><p><img src="https://bambooo.top/api/v3/objects/image/20260505130125086.webp"/>
<img src="https://bambooo.top/api/v3/objects/image/20260505130125085.webp"/>
<img src="https://bambooo.top/api/v3/objects/image/20260505130125084.webp"/>
<img src="https://bambooo.top/api/v3/objects/image/20260505130125083.webp"/>
<img src="https://bambooo.top/api/v3/objects/image/20260505130125082.webp"/>
<img src="https://bambooo.top/api/v3/objects/image/20260505130125081.webp"/>
<img src="https://bambooo.top/api/v3/objects/image/20260505130125080.webp"/>
<img src="https://bambooo.top/api/v3/objects/image/20260505130125079.webp"/>
<img src="https://bambooo.top/api/v3/objects/image/20260505130125078.webp"/>
<img src="https://bambooo.top/api/v3/objects/image/20260505130125077.webp"/>
<img src="https://bambooo.top/api/v3/objects/image/20260505130125076.webp"/>
<img src="https://bambooo.top/api/v3/objects/image/20260505130125075.webp"/>
<img src="https://bambooo.top/api/v3/objects/image/20260505130125074.webp"/>
<img src="https://bambooo.top/api/v3/objects/image/20260505130125073.webp"/>
<img src="https://bambooo.top/api/v3/objects/image/20260505130125072.webp"/></p><blockquote><p>小熊猫真的太可爱了</p></blockquote>
<p><img src="https://bambooo.top/api/v3/objects/image/20260505130125071.webp"/>
<img src="https://bambooo.top/api/v3/objects/image/20260505130125070.webp"/>
<img src="https://bambooo.top/api/v3/objects/image/20260505130125069.webp"/>
<img src="https://bambooo.top/api/v3/objects/image/20260505130125068.webp"/>
<img src="https://bambooo.top/api/v3/objects/image/20260505130125067.webp"/>
<img src="https://bambooo.top/api/v3/objects/image/20260505130125066.webp"/>
<img src="https://bambooo.top/api/v3/objects/image/20260505130125065.webp"/>
<img src="https://bambooo.top/api/v3/objects/image/20260505130125064.webp"/>
<img src="https://bambooo.top/api/v3/objects/image/20260505130125063.webp"/>
<img src="https://bambooo.top/api/v3/objects/image/20260505130125062.webp"/>
<img src="https://bambooo.top/api/v3/objects/image/20260505130125061.webp"/>
<img src="https://bambooo.top/api/v3/objects/image/20260505130125060.webp"/>
<img src="https://bambooo.top/api/v3/objects/image/20260505130125059.webp"/>
<img src="https://bambooo.top/api/v3/objects/image/20260505130125058.webp"/></p><h2 id="">玄武湖</h2><p><img src="https://bambooo.top/api/v3/objects/image/20260505130125057.webp"/>
<img src="https://bambooo.top/api/v3/objects/image/20260505130125056.webp"/>
<img src="https://bambooo.top/api/v3/objects/image/20260505130125055.webp"/>
<img src="https://bambooo.top/api/v3/objects/image/20260505130125054.webp"/>
<img src="https://bambooo.top/api/v3/objects/image/20260505130125053.webp"/></p></div><p style="text-align:right"><a href="https://bambooo.top/notes/4#comments">览毕，何不一言？</a></p></div>]]></description><link>https://bambooo.top/notes/4</link><guid isPermaLink="true">https://bambooo.top/notes/4</guid><dc:creator><![CDATA[扳布]]></dc:creator><pubDate>Tue, 05 May 2026 05:06:57 GMT</pubDate></item></channel></rss>