You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

1 lines
83 KiB

<!doctypehtml><html lang=zh-CN><meta charset=UTF-8><meta content=width=device-width name=viewport><meta media="(prefers-color-scheme: light)" content=#222 name=theme-color><meta media="(prefers-color-scheme: dark)" content=#222 name=theme-color><meta content="Hexo 7.3.0" name=generator><link href=https://cdnjs.cloudflare.com rel=preconnect><link href=/images/apple-touch-icon-next.png rel=apple-touch-icon sizes=180x180><link href=/images/favicon-32x32-next.png rel=icon sizes=32x32 type=image/png><link href=/images/favicon-16x16-next.png rel=icon sizes=16x16 type=image/png><link color=#222 href=/images/logo.svg rel=mask-icon><meta content=2X6S9P7CAjXjVvw8YyQR8pCu-B0oEu7O9quLgxXuWyA name=google-site-verification><meta content=dV8JGNzi0c name=baidu-site-verification><script>!function(){var t=!1;function e(){var e;t||(t=!0,(e=document.createElement("script")).setAttribute("data-ad-client","ca-pub-7480618470784058"),e.async=!0,e.src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js",document.head.appendChild(e))}function a(){"requestIdleCallback"in window?window.requestIdleCallback(e,{timeout:5e3}):window.setTimeout(e,5e3)}"complete"===document.readyState?a():window.addEventListener("load",a,{once:!0})}()</script><link href=/css/main.css rel=stylesheet><link crossorigin=anonymous href=https://cdnjs.cloudflare.com/ajax/libs/font-awesome/7.0.0/css/all.min.css integrity=sha256-VHqXKFhhMxcpubYf9xiWdCiojEbY9NexQ4jh8AxbvcM= rel=stylesheet><link crossorigin=anonymous href=https://cdnjs.cloudflare.com/ajax/libs/fancyapps-ui/6.1.14/fancybox/fancybox.css integrity=sha256-mdaMxrItEW6hMIVJJNPKuX0B7KCSJQVEv3mVY4N2we4= rel=stylesheet><script class=next-config data-name=main type=application/json>{"hostname":"nicksxs.me","root":"/","images":"/images","scheme":"Pisces","darkmode":true,"version":"8.29.0","exturl":false,"sidebar":{"position":"left","width_expanded":320,"width_dual_column":240,"display":"post","padding":18,"offset":12},"hljswrap":true,"codeblock":{"theme":{"light":"default","dark":"stackoverflow-dark"},"prism":{"light":"prism","dark":"prism-dark"},"copy_button":{"enable":true,"style":"default"},"fold":{"enable":false,"height":500},"language":false},"bookmark":{"enable":false,"color":"#222","save":"auto"},"mediumzoom":false,"pangu":false,"comments":{"style":"tabs","active":null,"storage":true,"lazyload":true,"nav":null},"stickytabs":false,"motion":{"enable":false,"async":false,"duration":200,"transition":{"menu_item":"fadeInDown","post_block":"fadeIn","post_header":"fadeInDown","post_body":"fadeInDown","coll_header":"fadeInLeft","sidebar":"fadeInUp"}},"prism":false,"i18n":{"placeholder":"搜索...","empty":"没有找到任何搜索结果:${query}","hits_time":"找到 ${hits} 个搜索结果(用时 ${time} 毫秒)","hits":"找到 ${hits} 个搜索结果"},"algolia":{"appID":"663Q99OQQD","apiKey":"e461a1371d12cec1491c1153b288a9ed","indexName":"nicksxs","hits":{"per_page":10}}}</script><script defer src=/js/config.js></script><meta content="前几天看到一个挺夸张的结果:FreeToken 在一台只有 8GB 显存的 RTX 4060 Laptop 上运行 Qwen3.6-35B-A3B,解码速度达到了 39.3 tok/s。" name=description><meta content=article property=og:type><meta content=8GB显存如何跑35B:深入理解FreeToken的专家缓存与CPU-GPU协同 property=og:title><meta content=https://nicksxs.me/2026/08/23/8GB%E6%98%BE%E5%AD%98%E5%A6%82%E4%BD%95%E8%B7%9135B-%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3FreeToken%E7%9A%84%E4%B8%93%E5%AE%B6%E7%BC%93%E5%AD%98%E4%B8%8ECPU-GPU%E5%8D%8F%E5%90%8C/index.html property=og:url><meta content="Nicksxs's Blog" property=og:site_name><meta content="前几天看到一个挺夸张的结果:FreeToken 在一台只有 8GB 显存的 RTX 4060 Laptop 上运行 Qwen3.6-35B-A3B,解码速度达到了 39.3 tok/s。" property=og:description><meta content=zh_CN property=og:locale><meta content=https://nicksxs.me/uploads/freetoken/dense-vs-moe.svg property=og:image><meta content=https://nicksxs.me/uploads/freetoken/memory-hierarchy.svg property=og:image><meta content=https://nicksxs.me/uploads/freetoken/decode-path.svg property=og:image><meta content=https://nicksxs.me/uploads/freetoken/prefill-double-buffer.svg property=og:image><meta content=https://nicksxs.me/uploads/freetoken/llamacpp-vs-freetoken.svg property=og:image><meta content=2026-08-23T12:41:00.000Z property=article:published_time><meta content=2026-08-23T12:41:00.000Z property=article:modified_time><meta content=Nicksxs property=article:author><meta content=LLM property=article:tag><meta content=FreeToken property=article:tag><meta content=llama.cpp property=article:tag><meta content=summary name=twitter:card><meta content=https://nicksxs.me/uploads/freetoken/dense-vs-moe.svg name=twitter:image><link href=https://nicksxs.me/2026/08/23/8GB%E6%98%BE%E5%AD%98%E5%A6%82%E4%BD%95%E8%B7%9135B-%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3FreeToken%E7%9A%84%E4%B8%93%E5%AE%B6%E7%BC%93%E5%AD%98%E4%B8%8ECPU-GPU%E5%8D%8F%E5%90%8C/ rel=canonical><script class=next-config data-name=page type=application/json>{"sidebar":"","isHome":false,"isPost":true,"lang":"zh-CN","comments":true,"permalink":"https://nicksxs.me/2026/08/23/8GB%E6%98%BE%E5%AD%98%E5%A6%82%E4%BD%95%E8%B7%9135B-%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3FreeToken%E7%9A%84%E4%B8%93%E5%AE%B6%E7%BC%93%E5%AD%98%E4%B8%8ECPU-GPU%E5%8D%8F%E5%90%8C/","path":"2026/08/23/8GB显存如何跑35B-深入理解FreeToken的专家缓存与CPU-GPU协同/","title":"8GB显存如何跑35B:深入理解FreeToken的专家缓存与CPU-GPU协同"}</script><script class=next-config data-name=calendar type=application/json>""</script><title>8GB显存如何跑35B:深入理解FreeToken的专家缓存与CPU-GPU协同 | Nicksxs's Blog</title><script async src=https://www.googletagmanager.com/gtag/js?id=UA-61358619-1></script><script class=next-config data-name=google_analytics type=application/json>{"tracking_id":"UA-61358619-1","only_pageview":false,"measure_protocol_api_secret":null}</script><script defer src=/js/third-party/analytics/google-analytics.js></script><script defer src=/js/third-party/analytics/baidu-analytics.js></script><script async src=https://hm.baidu.com/hm.js?20f33b3c0c0eff9b1522999c0015646d></script><script crossorigin=anonymous defer integrity=sha256-QnJV4HT5r8i89ovZGRD86iynZqHerEbT2xt69vDM36I= src=https://cdnjs.cloudflare.com/ajax/libs/fancyapps-ui/6.1.14/fancybox/fancybox.umd.js></script><script defer src=/js/utils.js></script><script defer src=/js/sidebar.js></script><script defer src=/js/next-boot.js></script><script data-algolia-integrity=sha256-o49zoHLDPBm9WLg6AiliAVi3axCRTqe3ltTxLvhzAVg= data-algolia-src=https://cdnjs.cloudflare.com/ajax/libs/algoliasearch/5.36.0/lite/builds/browser.umd.js data-search-src=/js/third-party/search/algolia-search.js src=/js/third-party/search/algolia-search-loader.js></script><script defer src=/js/third-party/fancybox.js></script><noscript><link href=/css/noscript.css rel=stylesheet></noscript><link title="Nicksxs's Blog" href=/atom.xml rel=alternate type=application/atom+xml><body itemscope itemtype=http://schema.org/WebPage><div class=headband></div><main class=main><div class=column><header class=header itemscope itemtype=http://schema.org/WPHeader><div class=site-brand-container><div class=site-nav-toggle><div aria-label=切换导航栏 class=toggle role=button><span class=toggle-line></span><span class=toggle-line></span><span class=toggle-line></span></div></div><div class=site-meta><a class=brand href=/ rel=start><i class=logo-line></i><p class=site-title>Nicksxs's Blog</p><i class=logo-line></i></a><p class=site-subtitle itemprop=description>Java 运行时、生产诊断与 AI 工程实践</div><div class=site-nav-right><div class="toggle popup-trigger" aria-label=搜索 role=button><i class="fa fa-search fa-fw fa-lg"></i></div></div></div><nav class=site-nav><ul class="main-menu menu"><li class="menu-item menu-item-home"><a href=/ rel=section><i class="fa fa-home fa-fw"></i>首页</a><li class="menu-item menu-item-专题"><a href=/topics/ rel=section><i class="fa fa-layer-group fa-fw"></i>专题</a><li class="menu-item menu-item-工具"><a href=/tools/ rel=section><i class="fa fa-toolbox fa-fw"></i>工具</a><li class="menu-item menu-item-about"><a href=/about/ rel=section><i class="fa fa-user fa-fw"></i>关于我</a><li class="menu-item menu-item-tags"><a href=/tags/ rel=section><i class="fa fa-tags fa-fw"></i>标签</a><li class="menu-item menu-item-categories"><a href=/categories/ rel=section><i class="fa fa-th fa-fw"></i>分类</a><li class="menu-item menu-item-archives"><a href=/archives/ rel=section><i class="fa fa-archive fa-fw"></i>归档</a><li class="menu-item menu-item-search"><a class=popup-trigger role=button><i class="fa fa-search fa-fw"></i>搜索</a></ul></nav><div class=search-pop-overlay><div class="popup search-popup"><div class=search-header><span class=search-icon><i class="fa fa-search"></i></span><div class=search-input-container><input autocapitalize=off autocomplete=off class=search-input maxlength=80 placeholder=搜索... spellcheck=false type=search></div><span class=popup-btn-close role=button><i class="fa fa-times-circle"></i></span></div><div class=search-result-container><div class=search-result-icon><i class="fab fa-algolia fa-5x"></i></div></div></div></div></header><aside class=sidebar><div class="sidebar-inner sidebar-nav-active sidebar-toc-active"><ul class=sidebar-nav><li class=sidebar-nav-toc>文章目录<li class=sidebar-nav-overview>站点概览</ul><div class=sidebar-panel-container><div class="post-toc-wrap sidebar-panel"><div class="post-toc animated"><ol class=nav><li class="nav-item nav-level-2"><a class=nav-link href=#%E5%85%88%E7%BA%A0%E6%AD%A3%E4%B8%80%E4%B8%AA%E5%AE%B9%E6%98%93%E6%B7%B7%E6%B7%86%E7%9A%84%E6%A8%A1%E5%9E%8B><span class=nav-number>1.</span> <span class=nav-text>先纠正一个容易混淆的模型</span></a><li class="nav-item nav-level-2"><a class=nav-link href=#35B-%E6%A8%A1%E5%9E%8B%E7%9A%84%E5%86%85%E5%AD%98%E8%B4%A6%E5%BA%94%E8%AF%A5%E6%80%8E%E4%B9%88%E7%AE%97><span class=nav-number>2.</span> <span class=nav-text>35B 模型的内存账应该怎么算</span></a><li class="nav-item nav-level-2"><a class=nav-link href=#FreeToken-%E7%9A%84%E5%9F%BA%E6%9C%AC%E7%BB%93%E6%9E%84><span class=nav-number>3.</span> <span class=nav-text>FreeToken 的基本结构</span></a><ol class=nav-child><li class="nav-item nav-level-3"><a class=nav-link href=#%E9%9D%9E%E4%B8%93%E5%AE%B6%E6%9D%83%E9%87%8D%E5%B8%B8%E9%A9%BB-GPU><span class=nav-number>3.1.</span> <span class=nav-text>非专家权重常驻 GPU</span></a><li class="nav-item nav-level-3"><a class=nav-link href=#%E5%AE%8C%E6%95%B4%E8%B7%AF%E7%94%B1%E4%B8%93%E5%AE%B6%E6%B1%A0%E5%B8%B8%E9%A9%BB%E5%86%85%E5%AD%98><span class=nav-number>3.2.</span> <span class=nav-text>完整路由专家池常驻内存</span></a><li class="nav-item nav-level-3"><a class=nav-link href=#%E5%89%A9%E4%BD%99%E6%98%BE%E5%AD%98%E7%BB%84%E6%88%90%E5%85%A8%E5%B1%80%E4%B8%93%E5%AE%B6%E7%BC%93%E5%AD%98><span class=nav-number>3.3.</span> <span class=nav-text>剩余显存组成全局专家缓存</span></a></ol><li class="nav-item nav-level-2"><a class=nav-link href=#%E7%94%9F%E6%88%90%E9%98%B6%E6%AE%B5%EF%BC%9A%E4%B8%93%E5%AE%B6%E7%BC%93%E5%AD%98%E5%88%B0%E5%BA%95%E6%80%8E%E4%B9%88%E5%B7%A5%E4%BD%9C><span class=nav-number>4.</span> <span class=nav-text>生成阶段:专家缓存到底怎么工作</span></a><li class="nav-item nav-level-2"><a class=nav-link href=#q-%EF%BC%9A%E4%B8%BA%E4%BB%80%E4%B9%88%E4%B8%8D%E6%98%AF%E4%B8%80%E5%8D%8A%E7%BB%99-CPU%E3%80%81%E4%B8%80%E5%8D%8A%E7%BB%99-GPU><span class=nav-number>5.</span> <span class=nav-text>q*:为什么不是一半给 CPU、一半给 GPU</span></a><li class="nav-item nav-level-2"><a class=nav-link href=#%E4%B8%BA%E4%BB%80%E4%B9%88-LRU-%E4%B8%93%E5%AE%B6%E7%BC%93%E5%AD%98%E4%BC%9A%E6%9C%89%E6%95%88><span class=nav-number>6.</span> <span class=nav-text>为什么 LRU 专家缓存会有效</span></a><li class="nav-item nav-level-2"><a class=nav-link href=#Prefill-%E4%B8%BA%E4%BB%80%E4%B9%88%E4%B8%8D%E8%83%BD%E4%B9%9F%E6%8C%89%E9%9C%80%E5%8A%A0%E8%BD%BD><span class=nav-number>7.</span> <span class=nav-text>Prefill 为什么不能也按需加载</span></a><li class="nav-item nav-level-2"><a class=nav-link href=#Agent-%E5%9C%BA%E6%99%AF%E9%87%8C%E8%BF%98%E6%9C%89%E4%B8%80%E7%A7%8D%E9%87%8D%E5%A4%8D%EF%BC%9A%E4%B8%8A%E4%B8%8B%E6%96%87%E8%A2%AB%E6%94%B9%E5%86%99><span class=nav-number>8.</span> <span class=nav-text>Agent 场景里还有一种重复:上下文被改写</span></a><li class="nav-item nav-level-2"><a class=nav-link href=#%E6%98%BE%E5%AD%98%E8%BF%98%E8%A6%81%E5%9C%A8%E4%B8%93%E5%AE%B6%E7%BC%93%E5%AD%98%E5%92%8C-KV-Cache-%E4%B9%8B%E9%97%B4%E5%8A%A8%E6%80%81%E5%88%86%E9%85%8D><span class=nav-number>9.</span> <span class=nav-text>显存还要在专家缓存和 KV Cache 之间动态分配</span></a><li class="nav-item nav-level-2"><a class=nav-link href=#FTW-%E4%B8%BA%E4%BB%80%E4%B9%88%E8%83%BD%E7%BC%A9%E7%9F%AD%E5%90%AF%E5%8A%A8%E6%97%B6%E9%97%B4><span class=nav-number>10.</span> <span class=nav-text>FTW 为什么能缩短启动时间</span></a><li class="nav-item nav-level-2"><a class=nav-link href=#%E4%BB%8E%E6%BA%90%E7%A0%81%E7%9C%8B%E8%BF%99%E4%BA%9B%E6%A8%A1%E5%9D%97%E6%80%8E%E4%B9%88%E5%AF%B9%E5%BA%94><span class=nav-number>11.</span> <span class=nav-text>从源码看这些模块怎么对应</span></a><li class="nav-item nav-level-2"><a class=nav-link href=#%E8%B7%9F-llama-cpp-%E7%9A%84%E9%83%A8%E5%88%86%E5%8D%B8%E8%BD%BD%E6%9C%89%E4%BB%80%E4%B9%88%E5%8C%BA%E5%88%AB><span class=nav-number>12.</span> <span class=nav-text>跟 llama.cpp 的部分卸载有什么区别</span></a><ol class=nav-child><li class="nav-item nav-level-3"><a class=nav-link href=#llama-cpp-%E7%9A%84-ngl-%E6%98%AF%E9%9D%99%E6%80%81%E5%B1%82%E5%8D%B8%E8%BD%BD><span class=nav-number>12.1.</span> <span class=nav-text>llama.cpp 的 -ngl 是静态层卸载</span></a><li class="nav-item nav-level-3"><a class=nav-link href=#llama-cpp-%E4%B9%9F%E6%9C%89%E4%B8%93%E9%97%A8%E7%9A%84-MoE-CPU-%E6%94%BE%E7%BD%AE><span class=nav-number>12.2.</span> <span class=nav-text>llama.cpp 也有专门的 MoE CPU 放置</span></a><li class="nav-item nav-level-3"><a class=nav-link href=#FreeToken-%E4%BC%98%E5%8C%96%E7%9A%84%E6%98%AF%E5%8A%A8%E6%80%81%E4%B8%93%E5%AE%B6%E5%B7%A5%E4%BD%9C%E9%9B%86><span class=nav-number>12.3.</span> <span class=nav-text>FreeToken 优化的是动态专家工作集</span></a></ol><li class="nav-item nav-level-2"><a class=nav-link href=#%E9%82%A3%E4%B9%88-Qwen3-8-27B-%E5%9C%A8-4060-%E4%B8%8A%E5%BA%94%E8%AF%A5%E7%94%A8%E5%93%AA%E4%B8%80%E7%A7%8D><span class=nav-number>13.</span> <span class=nav-text>那么 Qwen3.8-27B 在 4060 上应该用哪一种</span></a><li class="nav-item nav-level-2"><a class=nav-link href=#%E5%AE%9E%E9%99%85%E6%80%8E%E4%B9%88%E8%B7%91-FreeToken><span class=nav-number>14.</span> <span class=nav-text>实际怎么跑 FreeToken</span></a><li class="nav-item nav-level-2"><a class=nav-link href=#%E8%BF%99%E5%A5%97%E6%96%B9%E6%A1%88%E7%9A%84%E9%99%90%E5%88%B6><span class=nav-number>15.</span> <span class=nav-text>这套方案的限制</span></a><ol class=nav-child><li class="nav-item nav-level-3"><a class=nav-link href=#%E5%86%85%E5%AD%98%E4%BB%8D%E7%84%B6%E8%A6%81%E8%A3%85%E5%BE%97%E4%B8%8B%E5%AE%8C%E6%95%B4%E4%B8%93%E5%AE%B6%E6%B1%A0><span class=nav-number>15.1.</span> <span class=nav-text>内存仍然要装得下完整专家池</span></a><li class="nav-item nav-level-3"><a class=nav-link href=#%E4%B8%BB%E8%A6%81%E9%80%82%E7%94%A8%E4%BA%8E-MoE><span class=nav-number>15.2.</span> <span class=nav-text>主要适用于 MoE</span></a><li class="nav-item nav-level-3"><a class=nav-link href=#%E6%80%A7%E8%83%BD%E5%8F%97%E6%95%B4%E6%9C%BA%E8%80%8C%E4%B8%8D%E6%98%AF%E5%8F%AA%E5%8F%97-GPU-%E5%86%B3%E5%AE%9A><span class=nav-number>15.3.</span> <span class=nav-text>性能受整机而不是只受 GPU 决定</span></a><li class="nav-item nav-level-3"><a class=nav-link href=#39-3-tok-s-%E4%B8%8D%E6%98%AF%E6%89%80%E6%9C%89%E8%AF%B7%E6%B1%82%E7%9A%84%E5%9B%BA%E5%AE%9A%E9%80%9F%E5%BA%A6><span class=nav-number>15.4.</span> <span class=nav-text>39.3 tok/s 不是所有请求的固定速度</span></a><li class="nav-item nav-level-3"><a class=nav-link href=#%E7%BC%93%E5%AD%98%E4%B9%9F%E6%9C%89%E5%86%B7%E5%90%AF%E5%8A%A8%E5%92%8C%E5%B7%A5%E4%BD%9C%E9%9B%86%E7%AA%81%E5%8F%98><span class=nav-number>15.5.</span> <span class=nav-text>缓存也有冷启动和工作集突变</span></a></ol><li class="nav-item nav-level-2"><a class=nav-link href=#%E6%9C%80%E5%90%8E%E6%80%BB%E7%BB%93><span class=nav-number>16.</span> <span class=nav-text>最后总结</span></a><li class="nav-item nav-level-2"><a class=nav-link href=#%E5%8F%82%E8%80%83%E8%B5%84%E6%96%99><span class=nav-number>17.</span> <span class=nav-text>参考资料</span></a></ol></div></div><div class="site-overview-wrap sidebar-panel"><div class="site-author animated" itemprop=author itemscope itemtype=http://schema.org/Person><img alt=Nicksxs class=site-author-image itemprop=image src=/uploads/avatar.jpg><p class=site-author-name itemprop=name>Nicksxs<div class=site-description itemprop=description>Nicksxs 的技术博客,深入 Java 运行时与生产诊断、低成本大模型推理和 Agent 工程实践。</div></div><div class="site-state-wrap animated"><nav class=site-state><div class="site-state-item site-state-posts"><a href=/archives/><span class=site-state-item-count>394</span> <span class=site-state-item-name>日志</span></a></div><div class="site-state-item site-state-categories"><a href=/categories/><span class=site-state-item-count>181</span> <span class=site-state-item-name>分类</span></a></div><div class="site-state-item site-state-tags"><a href=/tags/><span class=site-state-item-count>347</span> <span class=site-state-item-name>标签</span></a></div></nav></div><div class="links-of-author animated"><span class=links-of-author-item><a rel="noopener me" title="GitHub → https://github.com/nicksxs" href=https://github.com/nicksxs target=_blank><i class="fab fa-github fa-fw"></i>GitHub</a> </span><span class=links-of-author-item><a rel="noopener me" title="E-Mail → mailto:nicksxs1202@gmail.com" href=mailto:nicksxs1202@gmail.com target=_blank><i class="fa fa-envelope fa-fw"></i>E-Mail</a></span></div><div class="cc-license animated" itemprop=license><a class=cc-opacity href=https://creativecommons.org/licenses/by-nc-sa/4.0/ rel=noopener target=_blank><img alt="Creative Commons" src=https://cdnjs.cloudflare.com/ajax/libs/creativecommons-vocabulary/2020.11.3/assets/license_badges/small/by_nc_sa.svg></a></div><div class="widget-wrap sidebar-tag-cloud"><div class=tag-cloud-tags><a class=tag-cloud-10 href=/tags/Java/ style=font-size:16px>Java</a><a class=tag-cloud-9 href=/tags/leetcode/ style=font-size:15.62px>leetcode</a><a class=tag-cloud-8 href=/tags/LLM/ style=font-size:15.23px>LLM</a><a class=tag-cloud-8 href=/tags/%E7%94%9F%E6%B4%BB/ style=font-size:14.85px>生活</a><a class=tag-cloud-7 href=/tags/%E9%A2%98%E8%A7%A3/ style=font-size:14.46px>题解</a><a class=tag-cloud-6 href=/tags/SpringBoot/ style=font-size:14.08px>SpringBoot</a><a class=tag-cloud-6 href=/tags/MySQL/ style=font-size:14.08px>MySQL</a><a class=tag-cloud-5 href=/tags/C/ style=font-size:13.69px>C++</a><a class=tag-cloud-5 href=/tags/Redis/ style=font-size:13.31px>Redis</a><a class=tag-cloud-4 href=/tags/Tomcat/ style=font-size:12.92px>Tomcat</a><a class=tag-cloud-4 href=/tags/Mybatis/ style=font-size:12.92px>Mybatis</a><a class=tag-cloud-3 href=/tags/%E5%B0%8F%E6%8A%80%E5%B7%A7/ style=font-size:12.54px>小技巧</a><a class=tag-cloud-2 href=/tags/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/ style=font-size:12.15px>数据结构</a><a class=tag-cloud-2 href=/tags/%E6%BA%90%E7%A0%81/ style=font-size:12.15px>源码</a><a class=tag-cloud-2 href=/tags/MQ/ style=font-size:11.77px>MQ</a><a class=tag-cloud-2 href=/tags/%E6%B6%88%E6%81%AF%E9%98%9F%E5%88%97/ style=font-size:11.77px>消息队列</a><a class=tag-cloud-2 href=/tags/RocketMQ/ style=font-size:11.77px>RocketMQ</a><a class=tag-cloud-2 href=/tags/%E8%BF%90%E5%8A%A8/ style=font-size:11.77px>运动</a><a class=tag-cloud-1 href=/tags/php/ style=font-size:11.38px>php</a><a class=tag-cloud-0 href=/tags/Spring/ style=font-size:11px>Spring</a><a class=tag-cloud-0 href=/tags/linux/ style=font-size:11px>linux</a><a class=tag-cloud-0 href=/tags/Docker/ style=font-size:11px>Docker</a><a class=tag-cloud-0 href=/tags/%E5%87%8F%E8%82%A5/ style=font-size:11px>减肥</a><a class=tag-cloud-0 href=/tags/%E8%B7%91%E6%AD%A5/ style=font-size:11px>跑步</a></div></div><div class="affiliate-creative affiliate-creative--sidebar" data-affiliate-id=tencent-cloud-server><a rel="sponsored noopener" aria-label=腾讯云服务器优惠 href=https://cloud.tencent.com/act/cps/redirect?redirect=6544&cps_key=2e33a1eedb380997d03618b4a5ba6ca7&from=console target=_blank><img alt="腾讯云 AI 焕新云服务器优惠" decoding=async height=241 loading=lazy src=/uploads/affiliate/tencent-cloud-server.webp width=690> </a><span class=affiliate-disclosure>推广</span></div></div></div></div><div class="sidebar-inner sidebar-blogroll"><div class="links-of-blogroll animated"><div class=links-of-blogroll-title><i class="fa fa-globe fa-fw"></i> 链接</div><ul class=links-of-blogroll-list><li class=links-of-blogroll-item><a href=https://covermusic.cn/ rel=noopener target=_blank title=https://covermusic.cn>69伙伴</a></ul></div></div></aside></div><div class="main-inner post posts-expand"><div class=post-block><article class=post-content itemscope itemtype=http://schema.org/Article lang=zh-CN><link href=https://nicksxs.me/2026/08/23/8GB%E6%98%BE%E5%AD%98%E5%A6%82%E4%BD%95%E8%B7%9135B-%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3FreeToken%E7%9A%84%E4%B8%93%E5%AE%B6%E7%BC%93%E5%AD%98%E4%B8%8ECPU-GPU%E5%8D%8F%E5%90%8C/ itemprop=mainEntityOfPage><span hidden itemprop=author itemscope itemtype=http://schema.org/Person><meta content=/uploads/avatar.jpg itemprop=image><meta content=Nicksxs itemprop=name></span><span hidden itemprop=publisher itemscope itemtype=http://schema.org/Organization><meta content="Nicksxs's Blog" itemprop=name><meta content="Nicksxs 的技术博客,深入 Java 运行时与生产诊断、低成本大模型推理和 Agent 工程实践。" itemprop=description></span><span hidden itemprop=post itemscope itemtype=http://schema.org/CreativeWork><meta content="8GB显存如何跑35B:深入理解FreeToken的专家缓存与CPU-GPU协同 | Nicksxs's Blog" itemprop=name><meta content="前几天看到一个挺夸张的结果:FreeToken 在一台只有 8GB 显存的 RTX 4060 Laptop 上运行 Qwen3.6-35B-A3B,解码速度达到了 39.3 tok/s。" itemprop=description></span><header class=post-header><h1 itemprop="name headline" class=post-title>8GB显存如何跑35B:深入理解FreeToken的专家缓存与CPU-GPU协同</h1><div class=post-meta-container><div class=post-meta><span class=post-meta-item><span class=post-meta-item-icon><i class="far fa-calendar"></i> </span><span class=post-meta-item-text>发表于</span> <time itemprop="dateCreated datePublished" title="创建时间:2026-08-23 20:41:00" datetime=2026-08-23T20:41:00+08:00>2026-08-23</time> </span><span class=post-meta-item><span class=post-meta-item-icon><i class="far fa-folder"></i> </span><span class=post-meta-item-text>分类于</span> <span itemprop=about itemscope itemtype=http://schema.org/Thing><a href=/categories/LLM/ itemprop=url rel=index><span itemprop=name>LLM</span></a> </span></span><span class="post-meta-item cloudflare-page-counter" data-increment=true data-path=/2026/08/23/8GB%E6%98%BE%E5%AD%98%E5%A6%82%E4%BD%95%E8%B7%9135B-%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3FreeToken%E7%9A%84%E4%B8%93%E5%AE%B6%E7%BC%93%E5%AD%98%E4%B8%8ECPU-GPU%E5%8D%8F%E5%90%8C/ data-title=8GB显存如何跑35B:深入理解FreeToken的专家缓存与CPU-GPU协同 title=阅读次数><span class=post-meta-item-icon><i class="far fa-eye"></i> </span><span class=post-meta-item-text>阅读次数:</span> <span class=cloudflare-page-counter-count></span></span></div><div class=post-description>前几天看到一个挺夸张的结果:FreeToken 在一台只有 8GB 显存的 RTX 4060 Laptop 上运行 Qwen3.6-35B-A3B,解码速度达到了 39.3 tok/s。</div></div></header><div class=post-body itemprop=articleBody><style>@media (max-width:767px){.post-body{min-width:0;overflow-x:clip}.post-body figure.highlight{max-width:100%;overflow-x:auto}.post-body>table{display:block;max-width:100%;overflow-x:auto}}</style><p>前几天看到一个挺夸张的结果:<blockquote><p>FreeToken 在一台只有 8GB 显存的 RTX 4060 Laptop 上运行 Qwen3.6-35B-A3B,解码速度达到了 39.3 tok/s。</blockquote><p>第一反应肯定是:35B 模型只算 4bit 权重,理论上也要 17.5GB 左右,8GB 显存怎么可能放得下?<p>后来翻了下 <a href=https://arxiv.org/abs/2608.16157 rel=noopener target=_blank>FreeToken 的论文</a>、<a href=https://github.com/FlashML-org/FreeToken rel=noopener target=_blank>源码</a>,又对比了下 llama.cpp,发现这个问题里最容易混淆的是两个完全不同的概念:<ul><li>模型能不能全部放进显存<li>生成一个 token 时,到底需要计算多少参数</ul><p>FreeToken 并没有把 35B 参数凭空塞进 8GB 显存里。它做的是把完整的 MoE 专家权重留在内存,把 GPU 显存变成一个会不断换入、换出的专家缓存,再让 CPU 和 GPU 一起处理缓存没有命中的专家。<p>先把结论用一句话说出来:<blockquote><p>MoE 让“大模型每次只用一小部分参数”成为可能,FreeToken 解决的则是“这一小部分参数怎样及时出现在最合适的计算设备上”。</blockquote><p>这篇就从这里展开。<h2 id=先纠正一个容易混淆的模型><a class=headerlink href=#先纠正一个容易混淆的模型 title=先纠正一个容易混淆的模型></a>先纠正一个容易混淆的模型</h2><p>之前讨论的是 <code>Qwen3.8-27B</code>,但是 FreeToken 论文里 8GB 显存运行 35B 的案例其实是:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br></pre><td class=code><pre><span class=line>Qwen3.6-35B-A3B</span><br></pre></table></figure><p>这两个模型虽然数字接近,结构却完全不同。<table><thead><tr><th>模型<th align=right>结构<th align=right>总参数<th align=right>每个 token 激活的参数<tbody><tr><td>Qwen3.8-27B<td align=right>Dense<td align=right>27B<td align=right>接近全部 27B<tr><td>Qwen3.6-35B-A3B<td align=right>MoE<td align=right>35B<td align=right>约 3B</table><p>Qwen3.8-27B 的官方模型卡明确写的是 Dense 模型。它有 64 层,每层的 FFN 都需要参与计算,没有“256 个专家只挑 8 个”这回事。<p>Qwen3.6-35B-A3B 则有 40 个 MoE 层,每层包含 256 个路由专家。一个 token 只会选择其中 8 个,再加上 1 个始终参与计算的共享专家。<p>名字里的:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br><span class=line>3</span><br></pre><td class=code><pre><span class=line>35B-A3B</span><br><span class=line> │ └── 每个 token 大约激活 3B 参数</span><br><span class=line> └────── 模型一共仍然有 35B 参数</span><br></pre></table></figure><p><code>A3B</code> 只是在说一次前向计算经过了多少参数,并不是模型文件只有 3B,也不是内存只需要保存 3B。<p><img alt="Dense 和 MoE 的计算路径" loading=lazy src=/uploads/freetoken/dense-vs-moe.svg><p>Dense 像是一整栋楼只有一条通道,每次都要从头走到尾。MoE 则像每层都有很多条支路,路由器会根据当前 token 选择其中几条。<p>这带来了两个结果:<ul><li>计算量可以接近一个 3B 模型<li>完整的 35B 权重仍然必须放在某个地方</ul><p>MoE 解决的是计算稀疏,并没有自动解决存储问题。FreeToken 真正处理的就是后半个问题。<h2 id=35B-模型的内存账应该怎么算><a title="35B 模型的内存账应该怎么算" class=headerlink href=#35B-模型的内存账应该怎么算></a>35B 模型的内存账应该怎么算</h2><p>先做一个很粗略的计算。假设不考虑量化元数据、对齐和少量高精度权重:<table><thead><tr><th>权重精度<th align=right>每个参数占用<th align=right>35B 参数的理论大小<tbody><tr><td>BF16 / FP16<td align=right>2 bytes<td align=right>70GB<tr><td>FP8 / INT8<td align=right>1 byte<td align=right>35GB<tr><td>4bit<td align=right>0.5 byte<td align=right>17.5GB</table><p>所以即使用 4bit,完整模型也明显超过 8GB。实际运行时还要额外留出:<ul><li>KV Cache<li>中间激活<li>CUDA Graph 和算子工作区<li>视觉编码器或者其他非专家权重<li>桌面环境占用的显存</ul><p>因此“8GB 运行 35B”绝对不能理解为“35B 的所有权重都常驻 8GB 显存”。<p>论文中的 4060 Laptop 机器实际有:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br><span class=line>3</span><br><span class=line>4</span><br></pre><td class=code><pre><span class=line>GPU: RTX 4060 Laptop,8GB VRAM</span><br><span class=line>内存: 32GiB LPDDR5</span><br><span class=line>PCIe: 4.0 x8</span><br><span class=line>模型: Qwen3.6-35B-A3B NVFP4</span><br></pre></table></figure><p>NVFP4 把专家权重压缩到了接近 4bit,完整专家池主要放在 32GiB 系统内存中。显存里放的是:<ol><li>注意力、路由器、归一化等非专家权重<li>KV Cache 和运行时工作区<li>一部分最近使用的专家</ol><p>所以这其实是一个三级存储问题:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br><span class=line>3</span><br><span class=line>4</span><br><span class=line>5</span><br></pre><td class=code><pre><span class=line>磁盘:保存模型文件</span><br><span class=line> ↓ 启动时读取</span><br><span class=line>内存:保存完整专家池,是权重的 source of truth</span><br><span class=line> ↓ 按需搬运</span><br><span class=line>显存:保存非专家权重、KV Cache 和热点专家</span><br></pre></table></figure><p>显存不再是模型的完整仓库,而是最快的一层缓存。<p><img alt="FreeToken 的内存层次" loading=lazy src=/uploads/freetoken/memory-hierarchy.svg><p>这跟操作系统的虚拟内存、CPU Cache 有一点像:完整数据在容量更大的慢速介质里,当前最可能使用的数据留在小而快的介质里。<p>不过模型推理有一个特别麻烦的地方:下一步会用到哪个专家,是运行到路由器之后才知道的。所以光有“缓存”两个字还不够,还需要解决命中、换入、并发和调度的问题。<h2 id=FreeToken-的基本结构><a title="FreeToken 的基本结构" class=headerlink href=#FreeToken-的基本结构></a>FreeToken 的基本结构</h2><p>FreeToken 把一个 MoE 模型拆成两部分:<h3 id=非专家权重常驻-GPU><a title="非专家权重常驻 GPU" class=headerlink href=#非专家权重常驻-GPU></a>非专家权重常驻 GPU</h3><p>注意力层、Gated DeltaNet、路由器、共享专家等不会像路由专家那样从几百个里面选几个。它们每次都会使用,所以尽量常驻 GPU。<h3 id=完整路由专家池常驻内存><a class=headerlink href=#完整路由专家池常驻内存 title=完整路由专家池常驻内存></a>完整路由专家池常驻内存</h3><p>所有层的全部专家都保留在 Host RAM 中。这一份是完整权重,也是最终兜底:<ul><li>GPU 缓存命中,直接在 GPU 上算<li>GPU 缓存未命中,可以把专家搬进显存<li>如果不值得搬,也可以直接由 CPU 计算</ul><p>因此 GPU 缓存大小只影响速度,不影响模型能不能算出正确结果。即使缓存是冷的,Host RAM 中仍然有完整专家。<h3 id=剩余显存组成全局专家缓存><a class=headerlink href=#剩余显存组成全局专家缓存 title=剩余显存组成全局专家缓存></a>剩余显存组成全局专家缓存</h3><p>FreeToken 没有给每一层固定切一块显存,而是把专家缓存做成跨层共享的 slot pool。<p>一个 slot 保存的是某一层某一个专家需要的全部权重:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br></pre><td class=code><pre><span class=line>(layer_id, expert_id) -> gpu_slot</span><br></pre></table></figure><p>以 Qwen3.6-35B-A3B 为例:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br></pre><td class=code><pre><span class=line>40 层 × 每层 256 个专家</span><br><span class=line>= 10240 个不同的 (layer, expert)</span><br></pre></table></figure><p>显存不需要有 10240 个 slot。假设当前只能放 600 个,就由所有层共同竞争这 600 个位置。最近使用过的专家留下,长时间没用的专家被淘汰。<p>这比“每层固定留 15 个专家”灵活,因为不同层、不同会话的热点分布并不平均。<h2 id=生成阶段:专家缓存到底怎么工作><a class=headerlink href=#生成阶段:专家缓存到底怎么工作 title=生成阶段:专家缓存到底怎么工作></a>生成阶段:专家缓存到底怎么工作</h2><p>大模型推理通常分成两个阶段:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br></pre><td class=code><pre><span class=line>Prefill:一次处理完整输入,得到第一个输出 token</span><br><span class=line>Decode:之后每轮只新增一个 token</span><br></pre></table></figure><p>这两个阶段虽然都经过 MoE 层,访问专家的模式却完全相反。先看 Decode。<p>假设某一层有 256 个专家,当前 token 被路由到了 8 个专家:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br></pre><td class=code><pre><span class=line>[17, 42, 58, 91, 103, 144, 201, 233]</span><br></pre></table></figure><p>FreeToken 会先查询每个专家是否在 GPU slot 中。<p>源码中的核心映射可以简化成:<figure class="highlight python"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br><span class=line>3</span><br><span class=line>4</span><br><span class=line>5</span><br><span class=line>6</span><br><span class=line>7</span><br><span class=line>8</span><br></pre><td class=code><pre><span class=line><span class=comment># 正向表:某层某专家当前在哪个 GPU slot</span></span><br><span class=line>slot_for_id[layer_id][expert_id] = slot_id</span><br><span class=line></span><br><span class=line><span class=comment># 反向表:某个 slot 当前装的是谁</span></span><br><span class=line>id_of_slot[slot_id] = layer_id * num_experts + expert_id</span><br><span class=line></span><br><span class=line><span class=comment># LRU 时间戳</span></span><br><span class=line>usage[slot_id] = current_step</span><br></pre></table></figure><p>一次查询之后会得到两组:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br></pre><td class=code><pre><span class=line>命中 H:已经在 GPU 中,可以立即计算</span><br><span class=line>未命中 M:仍然只在内存中</span><br></pre></table></figure><p>命中的部分很好处理。真正有意思的是未命中。<p>传统想法通常有两个极端:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br></pre><td class=code><pre><span class=line>方案 A:未命中专家全部通过 PCIe 搬到 GPU</span><br><span class=line>方案 B:未命中专家全部留在内存,由 CPU 计算</span><br></pre></table></figure><p>FreeToken 选择了第三种:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br></pre><td class=code><pre><span class=line>一部分搬到 GPU</span><br><span class=line>另一部分同时交给 CPU</span><br></pre></table></figure><p><img alt="FreeToken 的 Decode 路径" loading=lazy src=/uploads/freetoken/decode-path.svg><p>具体执行顺序大致是:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br><span class=line>3</span><br><span class=line>4</span><br><span class=line>5</span><br><span class=line>6</span><br><span class=line>7</span><br><span class=line>8</span><br><span class=line>9</span><br></pre><td class=code><pre><span class=line>1. 路由器给出逻辑 expert id</span><br><span class=line>2. 在全局 LRU 缓存中查找 slot</span><br><span class=line>3. 命中的专家直接改写为物理 slot id</span><br><span class=line>4. 为需要搬运的 miss 选择 LRU victim</span><br><span class=line>5. 先提交 CPU 专家计算</span><br><span class=line>6. 同时通过 PCIe 把另一部分 miss 搬进 GPU slot</span><br><span class=line>7. GPU 计算“原有 hit + 新填入的专家”</span><br><span class=line>8. 等待 CPU 部分完成</span><br><span class=line>9. 把 CPU 和 GPU 的部分结果相加</span><br></pre></table></figure><p>对应到源码,<code>python/freetoken/layers/moe.py</code> 中的 <code>_decode_hybrid()</code> 做的正是这件事。它先提交 CPU task,再调用 <code>copy_missing()</code> 和 GPU expert GEMM,最后:<figure class="highlight python"><table><tr><td class=gutter><pre><span class=line>1</span><br></pre><td class=code><pre><span class=line><span class=keyword>return</span> gpu_routed + cpu_routed</span><br></pre></table></figure><p>为什么可以直接相加?<p>一个标准 MoE 层的结果本来就是多个专家输出的加权和:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br></pre><td class=code><pre><span class=line>y = g1 × E1(x) + g2 × E2(x) + ... + gk × Ek(x)</span><br></pre></table></figure><p>把专家集合拆成 GPU 集合和 CPU 集合,只是改变了计算地点:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br><span class=line>3</span><br><span class=line>4</span><br></pre><td class=code><pre><span class=line>y_gpu = GPU 上专家的加权和</span><br><span class=line>y_cpu = CPU 上专家的加权和</span><br><span class=line></span><br><span class=line>y = y_gpu + y_cpu</span><br></pre></table></figure><p>FreeToken 会保证一条路由只由一边计算一次。源码中,交给 CPU 的专家在 GPU 路径里权重会被置零,交给 GPU 的专家在 CPU 路径里会被标记成 <code>-1</code> 并跳过。<p>所以这里不是删掉几个专家,也不是近似计算。除了模型本身使用的量化精度,CPU/GPU 分流不会额外改变 MoE 的数学定义。<h2 id=q-:为什么不是一半给-CPU、一半给-GPU><a title="q*:为什么不是一半给 CPU、一半给 GPU" class=headerlink href=#q-:为什么不是一半给-CPU、一半给-GPU></a>q*:为什么不是一半给 CPU、一半给 GPU</h2><p>现在还剩一个问题:假设有 4 个专家没有命中,到底应该搬几个?<p>如果全部搬运,CPU 可能在旁边闲着。如果全部交给 CPU,PCIe 和 GPU 又会闲着。最合适的比例跟机器有关。<p>FreeToken 用两个实际测出来的带宽做决定:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br><span class=line>3</span><br><span class=line>4</span><br></pre><td class=code><pre><span class=line>B_P:内存通过 PCIe 向 GPU 搬专家的有效带宽</span><br><span class=line>B_H:CPU 直接读取并计算专家时的有效内存带宽</span><br><span class=line>m:这一层没有命中的专家数量</span><br><span class=line>q:其中选择搬入 GPU 的数量</span><br></pre></table></figure><p>注意,PCIe DMA 和 CPU 计算都要从同一份内存中读专家权重。PCIe 正在以 <code>B_P</code> 读数据时,能留给 CPU 的大约是:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br></pre><td class=code><pre><span class=line>B_R = B_H - B_P</span><br></pre></table></figure><p>假设每个专家大小都是 <code>S</code>,两个并发分支的耗时近似为:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br></pre><td class=code><pre><span class=line>GPU 搬运分支:T_fill ≈ q × S / B_P</span><br><span class=line>CPU 计算分支:T_cpu ≈ (m - q) × S / (B_H - B_P)</span><br></pre></table></figure><p>两个任务是并行的,总时间取较慢的那个。要让硬件利用得最充分,就尽量让它们同时结束:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br></pre><td class=code><pre><span class=line>q × S / B_P ≈ (m - q) × S / (B_H - B_P)</span><br></pre></table></figure><p>两边约掉 <code>S</code>,整理一下:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br></pre><td class=code><pre><span class=line>q* ≈ m × B_P / B_H</span><br></pre></table></figure><p>这就是论文中的 <code>q* policy</code>。<p>这个推导默认 <code>B_H > B_P</code>,也就是 PCIe 占满以后还有一部分 Host 带宽能留给 CPU。实际执行时还会把 <code>q</code> 取整并限制在合法范围。对于 <code>m > 0</code> 的情况,可以把它理解成:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br></pre><td class=code><pre><span class=line>q = clamp(round(q*), 1, m)</span><br></pre></table></figure><p>当 <code>B_H</code> 接近或者低于 <code>B_P</code> 时,CPU 已经没有值得利用的剩余带宽,策略就退化成 <code>q = m</code>,把 miss 都送去 GPU;至少保留一次 fill,也能让冷缓存继续升温。<p>用论文里 4060 Laptop 的实测数据算一次:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br><span class=line>3</span><br><span class=line>4</span><br><span class=line>5</span><br><span class=line>6</span><br></pre><td class=code><pre><span class=line>B_P = 11.8 GB/s</span><br><span class=line>B_H = 47.5 GB/s</span><br><span class=line>m = 4</span><br><span class=line></span><br><span class=line>q* ≈ 4 × 11.8 / 47.5</span><br><span class=line> ≈ 0.99</span><br></pre></table></figure><p>取整之后就是:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br></pre><td class=code><pre><span class=line>1 个专家通过 PCIe 搬进 GPU</span><br><span class=line>3 个专家直接由 CPU 计算</span><br></pre></table></figure><p>这里有一个挺反直觉的地方:CPU 并不是因为算力特别强才参与,而是因为 Decode 的 batch 通常很小,专家 GEMV 更容易受内存带宽限制。CPU 计算那 3 个专家,实际上是在利用 PCIe 没有吃掉的剩余内存带宽。<p>如果换到另一台机器:<ul><li>PCIe 很快、CPU 内存带宽不高,<code>q*</code> 会接近 <code>m</code>,更适合多搬到 GPU<li>内存带宽明显高于 PCIe,CPU 可以承接更多 miss<li>专家本来就在缓存里,则根本不进入 <code>q*</code> 分流</ul><p>所以 FreeToken 不靠显卡型号表拍脑袋。它提供:<figure class="highlight bash"><table><tr><td class=gutter><pre><span class=line>1</span><br></pre><td class=code><pre><span class=line>ft bench bw</span><br></pre></table></figure><p>这个命令使用真实的专家搬运和 CPU MoE kernel 测量带宽,并把结果写到:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br></pre><td class=code><pre><span class=line>~/.cache/freetoken/benchbw.json</span><br></pre></table></figure><p><code>ft serve --moe-backend auto</code> 会根据这份 profile 决定继续使用 offload,还是升级为 CPU/GPU 并行的 hybrid。<h2 id=为什么-LRU-专家缓存会有效><a title="为什么 LRU 专家缓存会有效" class=headerlink href=#为什么-LRU-专家缓存会有效></a>为什么 LRU 专家缓存会有效</h2><p>如果每个 token 选中的专家完全随机,刚放进 GPU 的专家下一步就再也不用,那么缓存没有太大价值。<p>实际路由通常存在短期局部性。相邻 token 处在同一段语义、同一种语言或同一段代码中,路由到的专家经常重叠:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br><span class=line>3</span><br></pre><td class=code><pre><span class=line>token t: [17, 42, 58, 91, 103, 144, 201, 233]</span><br><span class=line>token t + 1: [17, 42, 61, 91, 103, 144, 205, 233]</span><br><span class=line> ↑ ↑ ↑ ↑ ↑ ↑</span><br></pre></table></figure><p>第二个 token 中就有多个专家可以直接命中。<p>FreeToken 使用全局 LRU:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br><span class=line>3</span><br><span class=line>4</span><br></pre><td class=code><pre><span class=line>命中:更新 usage</span><br><span class=line>未命中:找最久没有使用的 slot</span><br><span class=line>换入:更新正向表和反向表</span><br><span class=line>执行:把逻辑 expert id 改写为 slot id</span><br></pre></table></figure><p>源码里的实现还多做了一步:命中判断、miss 去重、<code>q</code> 的计算、victim 选择和 ID 改写都在 GPU kernel 中完成。<p>原因是 CUDA Graph 喜欢固定的控制流和固定形状。如果每一层都把 expert id 拷回 CPU,让 Python 决定淘汰谁,再通知 GPU,光同步就会非常昂贵。<p>FreeToken 的做法是:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br></pre><td class=code><pre><span class=line>控制流保持固定</span><br><span class=line>变化的命中数、miss 数和 victim 都作为 GPU 上的数据</span><br></pre></table></figure><p>工作缓冲区大小固定,再用一个有效数量屏蔽没有使用的部分。这样动态 LRU 仍然可以被放进静态捕获的 CUDA Graph。<p>论文给出的路由回放结果也能看到这个差别。在相同缓存容量下,Qwen3.6 的 Decode miss rate:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br><span class=line>3</span><br></pre><td class=code><pre><span class=line>FreeToken 全局 LRU:16%</span><br><span class=line>KTransformers 预填充时更新的静态放置:41%</span><br><span class=line>llama.cpp 路由无关的静态切分:62%</span><br></pre></table></figure><p>这个数字不是说 LRU 永远都会命中 84%,而是说明在论文的四类 agent workload 和指定缓存容量下,token 之间确实存在可以利用的路由局部性。<h2 id=Prefill-为什么不能也按需加载><a title="Prefill 为什么不能也按需加载" class=headerlink href=#Prefill-为什么不能也按需加载></a>Prefill 为什么不能也按需加载</h2><p>看完 Decode 很容易产生一个想法:Prefill 也让选中的专家按需进入缓存不就行了?<p>问题在于 Prefill 不是一次只处理一个 token,而是一次处理几千个 token。<p>对单个 token 来说:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br></pre><td class=code><pre><span class=line>256 个专家中只选 8 个</span><br></pre></table></figure><p>但是一批 8192 个 token 各选 8 个,取所有路由结果的并集后,几乎每个专家都会被碰到:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br><span class=line>3</span><br><span class=line>4</span><br><span class=line>5</span><br><span class=line>6</span><br></pre><td class=code><pre><span class=line>token 1 -> 8 个专家</span><br><span class=line>token 2 -> 另外一些专家</span><br><span class=line>...</span><br><span class=line>token 8192 -> 另外一些专家</span><br><span class=line></span><br><span class=line>所有 token 的并集 -> 接近完整 256 个专家</span><br></pre></table></figure><p>也就是说,MoE 在 Decode 阶段是稀疏工作集,到了长 Prompt 的 Prefill 阶段却接近稠密工作集。<p>如果还逐个 miss、逐个搬运,会产生很多细碎传输,GPU 也会不断等 PCIe。<p>FreeToken 因此给 Prefill 设计了另一条路径:完整层双缓冲。<p><img alt="FreeToken 的 Prefill 双缓冲" loading=lazy src=/uploads/freetoken/prefill-double-buffer.svg><p>它从全局专家 slot pool 中借出能容纳两层完整专家的空间:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br></pre><td class=code><pre><span class=line>Buffer A:GPU 正在计算第 l 层</span><br><span class=line>Buffer B:PCIe 同时传输第 l + 1 层</span><br></pre></table></figure><p>第 <code>l</code> 层算完后,两块 buffer 交换角色:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br></pre><td class=code><pre><span class=line>Buffer B:计算第 l + 1 层</span><br><span class=line>Buffer A:传输第 l + 2 层</span><br></pre></table></figure><p>因为搬的是下一层完整专家,甚至不需要等下一层路由结果出来就可以提前开始传输。理想情况下,一层的计算时间被下一层的搬运时间完全覆盖。<p>这也解释了为什么至少要有两层专家大小的缓存才能开启 overlap。源码中 <code>OffloadMoeCache</code> 会直接检查:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br></pre><td class=code><pre><span class=line>cache_size >= 2 × num_experts</span><br></pre></table></figure><p>空间不足时不会硬撑,而是回退到按需 Prefill。<p>论文在 RTX 5090 上关闭双缓冲后,4K、8K、16K Prompt 的 Prefill 吞吐分别下降了 19%、25% 和 26%。Prompt 越长,完整专家池的搬运越无法忽略。<h2 id=Agent-场景里还有一种重复:上下文被改写><a title="Agent 场景里还有一种重复:上下文被改写" class=headerlink href=#Agent-场景里还有一种重复:上下文被改写></a>Agent 场景里还有一种重复:上下文被改写</h2><p>普通聊天通常只是在历史后面追加内容,前缀 KV Cache 很容易复用。<p>Coding Agent 不一样。一次工具调用之后,框架可能会:<ul><li>删除旧的 thinking<li>用占位符替换太老的工具输出<li>截断历史 observation<li>保留前半段,再改写中间某一块</ul><p>Qwen3.6 这类混合架构不仅有完整注意力,还有 Gated DeltaNet 这样的递归状态。KV Cache 可以按 token 保存,递归层却把前缀压缩成一个不断演进的 state。<p>如果中间一块上下文被改了,修改点后面的 state 都失效。检查点又很少的话,只能从很早的位置重新 Prefill。<p>FreeToken 的做法是把有限的 recurrent-state checkpoint 放在更可能存活的语义边界:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br><span class=line>3</span><br><span class=line>4</span><br></pre><td class=code><pre><span class=line>一轮对话结束</span><br><span class=line>thinking 结束</span><br><span class=line>tool call 开始/结束</span><br><span class=line>tool output 开始/结束</span><br></pre></table></figure><p>Agent 框架通常也是按这些完整区块删除内容,所以边界之前的前缀更可能保持不变。恢复时:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br><span class=line>3</span><br></pre><td class=code><pre><span class=line>找到仍然有效的最深语义锚点</span><br><span class=line>恢复对应的 KV / recurrent state</span><br><span class=line>只重新计算变化后的后缀</span><br></pre></table></figure><p>这部分跟专家缓存解决的不是同一个问题:<table><thead><tr><th>缓存<th>缓存的东西<th>主要减少什么<tbody><tr><td>专家缓存<td>MoE 权重<td>Decode 时的 PCIe 搬运和 CPU 计算<tr><td>KV / 状态缓存<td>Prompt 的计算结果<td>多轮 Agent 中重复 Prefill</table><p>FreeToken 把它们放进同一个 serving engine,是因为本地 Agent 的总等待时间同时受两边影响。只有 Decode 跑得快,但每次工具调用后重新 Prefill 一分钟,体验仍然不行。<h2 id=显存还要在专家缓存和-KV-Cache-之间动态分配><a title="显存还要在专家缓存和 KV Cache 之间动态分配" class=headerlink href=#显存还要在专家缓存和-KV-Cache-之间动态分配></a>显存还要在专家缓存和 KV Cache 之间动态分配</h2><p>刚启动模型时上下文很短,可以把更多剩余显存用作专家缓存。对话越来越长后,KV Cache 会持续增长,而专家工作集没有同比增长。<p>静态切分很容易出现:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br><span class=line>3</span><br></pre><td class=code><pre><span class=line>专家缓存很大,但 KV Cache 不够</span><br><span class=line>或者</span><br><span class=line>KV 预留太多,专家缓存命中率很低</span><br></pre></table></figure><p>FreeToken 把这两块都看成运行时资源。在 scheduler 没有待处理 Prefill、没有进行中的 Decode 时,到达一个 safe point,才执行 cache rebuild:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br><span class=line>3</span><br><span class=line>4</span><br><span class=line>5</span><br></pre><td class=code><pre><span class=line>暂停接收新的计算批次</span><br><span class=line>释放旧 CUDA Graph</span><br><span class=line>重新分配专家 slot 和 KV page</span><br><span class=line>重新捕获 CUDA Graph</span><br><span class=line>继续服务</span><br></pre></table></figure><p>Host RAM 中的完整专家池不需要重新加载,所以不必重启进程或者再次读取整个模型。<p>可以通过:<figure class="highlight bash"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br></pre><td class=code><pre><span class=line>ft ctl cache</span><br><span class=line>ft ctl cache --moe 512 --kv 16k</span><br></pre></table></figure><p>查看和调整缓存。调整不是发生在某个 token 计算到一半的时候,而是排队等到 scheduler 完全空闲的安全点。<h2 id=FTW-为什么能缩短启动时间><a title="FTW 为什么能缩短启动时间" class=headerlink href=#FTW-为什么能缩短启动时间></a>FTW 为什么能缩短启动时间</h2><p>模型文件里的专家权重未必正好是运行时最方便的排列。FreeToken 会把不同模型的专家整理成少数几种 expert bank,并把:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br></pre><td class=code><pre><span class=line>flat_id = layer_id × num_experts + expert_id</span><br></pre></table></figure><p>作为每个 bank 的第一维。<p>同一个 <code>flat_id</code> 在 <code>gate_up</code>、<code>down</code>、scale 等 bank 中对应同一个完整专家。这样 GPU slot cache 和 CPU executor 共用一套逻辑 ID,不需要理解每种模型原始 checkpoint 的布局。<p>FTW 是 FreeToken 的 fast weight format。它不是新的量化算法,主要作用是预先把权重转换成运行时最终布局。<p>传统加载过程可能是:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br><span class=line>3</span><br><span class=line>4</span><br><span class=line>5</span><br></pre><td class=code><pre><span class=line>申请巨大空缓冲区</span><br><span class=line>把内存页 pin 住并清零</span><br><span class=line>读取 checkpoint</span><br><span class=line>重新排列专家</span><br><span class=line>复制到最终位置</span><br></pre></table></figure><p>FreeToken 的路径是:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br></pre><td class=code><pre><span class=line>从磁盘直接读入最终 Host 布局</span><br><span class=line>数据填充完成后再 pin</span><br></pre></table></figure><p>Pinned memory,也就是 page-locked memory,不会被操作系统随便换出,CUDA 可以对它进行更稳定的异步 DMA。但提前 pin 一个巨大的空缓冲区会触发页面分配和清零,随后又马上被模型数据覆盖,白做了一遍内存写入。<p>FTW 避免了这部分启动开销。GPU 专家缓存也不需要专门 warmup,第一次请求从冷缓存开始,沿用普通 miss 路径自然升温。<p><code>ft checkpoint</code> 是可选步骤,FreeToken 也能直接读取支持的 Hugging Face checkpoint:<figure class="highlight bash"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br><span class=line>3</span><br><span class=line>4</span><br></pre><td class=code><pre><span class=line>ft checkpoint \</span><br><span class=line> --model ~/models/Qwen3.6-35B-A3B \</span><br><span class=line> --out ~/models/Qwen3.6-35B-A3B-FTW \</span><br><span class=line> --moe-backend offload</span><br></pre></table></figure><h2 id=从源码看这些模块怎么对应><a class=headerlink href=#从源码看这些模块怎么对应 title=从源码看这些模块怎么对应></a>从源码看这些模块怎么对应</h2><p>把论文和代码对照起来,大致是下面这些位置:<table><thead><tr><th>源码<th>主要职责<tbody><tr><td><code>python/freetoken/moe/offload_cache.py</code><td>全局专家 slot、LRU 映射、专家 bank、Prefill 双缓冲<tr><td><code>python/freetoken/moe/offload_kernels.py</code><td>GPU 上做 hit/miss 分类、<code>q</code> 计算、victim 选择和 ID 改写<tr><td><code>python/freetoken/layers/moe.py</code><td>串起路由、CPU 提交、PCIe copy、GPU GEMM 和结果合并<tr><td><code>python/freetoken/moe/cpu_executor.py</code><td>固定 CPU worker、SIMD 和量化专家计算<tr><td><code>python/freetoken/moe/benchbw.py</code><td>测量真实 CPU MoE 与 PCIe 搬运带宽<tr><td><code>python/freetoken/engine/cache_budget.py</code><td>在专家 slot 和 KV page 之间计算显存预算<tr><td><code>python/freetoken/scheduler/scheduler.py</code><td>在 idle safe point 执行运行时 cache rebuild</table><p>其中最核心的 Decode 伪代码其实不复杂:<figure class="highlight python"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br><span class=line>3</span><br><span class=line>4</span><br><span class=line>5</span><br><span class=line>6</span><br><span class=line>7</span><br><span class=line>8</span><br><span class=line>9</span><br><span class=line>10</span><br><span class=line>11</span><br><span class=line>12</span><br></pre><td class=code><pre><span class=line>routes = router(hidden_states)</span><br><span class=line></span><br><span class=line>hits, misses = gpu_cache.lookup(routes)</span><br><span class=line>to_gpu, to_cpu = split_by_bandwidth(misses)</span><br><span class=line></span><br><span class=line>cpu_task = cpu_executor.submit(to_cpu)</span><br><span class=line>gpu_cache.fill(to_gpu)</span><br><span class=line></span><br><span class=line>gpu_output = gpu_experts(hits + to_gpu)</span><br><span class=line>cpu_output = cpu_task.wait()</span><br><span class=line></span><br><span class=line><span class=keyword>return</span> gpu_output + cpu_output</span><br></pre></table></figure><p>真正难的是让 lookup、动态 miss 数、异步拷贝、CPU worker 和 CUDA Graph 同时成立,而且不能每层都把控制权交回 Python。<h2 id=跟-llama-cpp-的部分卸载有什么区别><a title="跟 llama.cpp 的部分卸载有什么区别" class=headerlink href=#跟-llama-cpp-的部分卸载有什么区别></a>跟 llama.cpp 的部分卸载有什么区别</h2><p>两者最表面的相同点都是:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br></pre><td class=code><pre><span class=line>显存不够,就让一部分权重留在内存</span><br></pre></table></figure><p>但“哪一部分留在内存”和“运行时怎么使用它”差别很大。<p><img alt="llama.cpp 与 FreeToken 的卸载方式" loading=lazy src=/uploads/freetoken/llamacpp-vs-freetoken.svg><h3 id=llama-cpp-的-ngl-是静态层卸载><a title="llama.cpp 的 -ngl 是静态层卸载" class=headerlink href=#llama-cpp-的-ngl-是静态层卸载></a>llama.cpp 的 -ngl 是静态层卸载</h3><p>以一个 64 层 Dense 模型为例:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br></pre><td class=code><pre><span class=line>第 0~39 层:CPU</span><br><span class=line>第 40~63 层:GPU</span><br></pre></table></figure><p>模型加载完成后,层的位置基本就固定了。每生成一个 token,仍然要依次经过全部 64 层:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br><span class=line>3</span><br></pre><td class=code><pre><span class=line>CPU 计算前 40 层</span><br><span class=line>传递中间激活</span><br><span class=line>GPU 计算后 24 层</span><br></pre></table></figure><p>这里通常移动的是层与层之间较小的激活,不是每个 token 都把完整层权重搬来搬去。<p>这种方式非常适合 Dense 模型,因为 Dense 没有路由器,任何一层的 FFN 权重每个 token 都会使用。也就谈不上“缓存最近用到的专家”。<p>当前 llama.cpp 的参数是:<figure class="highlight bash"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br><span class=line>3</span><br></pre><td class=code><pre><span class=line>-ngl N</span><br><span class=line>--gpu-layers N</span><br><span class=line>--n-gpu-layers N</span><br></pre></table></figure><p>现在也可以使用 <code>auto</code> 或 <code>all</code>,让运行时自动拟合显存。<h3 id=llama-cpp-也有专门的-MoE-CPU-放置><a title="llama.cpp 也有专门的 MoE CPU 放置" class=headerlink href=#llama-cpp-也有专门的-MoE-CPU-放置></a>llama.cpp 也有专门的 MoE CPU 放置</h3><p>如果模型是 MoE,当前 llama.cpp 还支持:<figure class="highlight bash"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br></pre><td class=code><pre><span class=line>-cmoe</span><br><span class=line>--cpu-moe</span><br></pre></table></figure><p>把全部 MoE 专家权重留在 CPU;或者:<figure class="highlight bash"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br></pre><td class=code><pre><span class=line>-ncmoe N</span><br><span class=line>--n-cpu-moe N</span><br></pre></table></figure><p>只把前 N 个 MoE 层的专家留在 CPU。<p>这已经比单纯的 <code>-ngl</code> 更接近 FreeToken:注意力等非专家部分可以继续在 GPU,而巨大的专家权重留在内存。<p>llama.cpp 当前的 scheduler 还能根据路由 ID,只处理被选中的 expert sub-row,而不是无条件复制所有专家。不过截至 2026 年 8 月 23 日,官方 master 的这条路径没有 FreeToken 那样跨 token 持久存在的 <code>expert -> slot</code> 映射。被选中的专家可以在一次 forward 中搬到 GPU tensor 的原始偏移位置,但下一次相同专家仍可能再次搬运。<p>llama.cpp 社区已经有 persistent expert cache 的 RFC 和 PR,例如 <a href=https://github.com/ggml-org/llama.cpp/issues/20757 rel=noopener target=_blank>#20757</a> 和 <a href=https://github.com/ggml-org/llama.cpp/pull/26824 rel=noopener target=_blank>#26824</a>,说明这个方向也正在快速演进。这里对比的是本文日期的官方 CLI 和 master 行为,不应该理解成 llama.cpp 永远不会有专家缓存。<h3 id=FreeToken-优化的是动态专家工作集><a title="FreeToken 优化的是动态专家工作集" class=headerlink href=#FreeToken-优化的是动态专家工作集></a>FreeToken 优化的是动态专家工作集</h3><p>FreeToken 的主要单位不是“第几层放在哪”,而是:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br></pre><td class=code><pre><span class=line>当前这个 (layer, expert) 应该在哪里执行</span><br></pre></table></figure><table><thead><tr><th>对比项<th>llama.cpp <code>-ngl</code><th>llama.cpp <code>--cpu-moe</code><th>FreeToken hybrid<tbody><tr><td>主要对象<td>完整层<td>MoE 专家 tensor<td>单个 <code>(layer, expert)</code><tr><td>放置时机<td>加载时静态决定<td>加载时决定专家在 CPU<td>每个 Decode step 动态决定<tr><td>GPU 专家驻留<td>不适用<td>官方 master 无跨 step slot cache<td>全局 LRU 持久缓存<tr><td>Cache miss<td>不适用<td>CPU 计算或按路由搬运<td>一部分搬 GPU,一部分 CPU 计算<tr><td>CPU/GPU 比例<td>由层数决定<td>由 CPU MoE 层数等配置决定<td><code>q*</code> 根据实测带宽决定<tr><td>Prefill<td>CPU/GPU 按层执行<td>后端相关的专家处理<td>完整层双缓冲隐藏 PCIe<tr><td>Agent 状态复用<td>KV/Context checkpoint<td>KV/Context checkpoint<td>KV + 语义 recurrent-state anchor<tr><td>格式和平台<td>GGUF,CPU/CUDA/Metal/Vulkan 等<td>同左<td>当前开源 CLI 主要是 Linux x86_64 + NVIDIA CUDA</table><p>所以它们不是简单的“一个能卸载,一个不能卸载”。<p>llama.cpp 是通用、本地部署范围很广的推理引擎,Dense 模型、Apple Silicon 和各种后端都是它的优势。FreeToken 则把目标收得更窄,专门围绕“显存装不下完整 MoE 专家池”设计整个 serving stack。<h2 id=那么-Qwen3-8-27B-在-4060-上应该用哪一种><a title="那么 Qwen3.8-27B 在 4060 上应该用哪一种" class=headerlink href=#那么-Qwen3-8-27B-在-4060-上应该用哪一种></a>那么 Qwen3.8-27B 在 4060 上应该用哪一种</h2><p>回到最开始容易串在一起的问题。<p>Qwen3.8-27B 是 Dense 模型:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br></pre><td class=code><pre><span class=line>没有 256 选 8 的路由专家</span><br><span class=line>每个 token 都要经过每层完整 FFN</span><br></pre></table></figure><p>27B 做 4bit 的理论权重大小约为:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br></pre><td class=code><pre><span class=line>27B × 0.5 byte = 13.5GB</span><br></pre></table></figure><p>再加量化元数据、KV Cache 和运行时开销,8GB 4060 无法让它全量常驻显存。<p>这时更合适的思路是:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br><span class=line>3</span><br><span class=line>4</span><br></pre><td class=code><pre><span class=line>GGUF 低比特量化</span><br><span class=line>+ llama.cpp</span><br><span class=line>+ 尽可能多的静态 GPU layer offload</span><br><span class=line>+ 剩余层在 CPU / RAM 中执行</span><br></pre></table></figure><p>例如:<figure class="highlight bash"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br><span class=line>3</span><br><span class=line>4</span><br></pre><td class=code><pre><span class=line>llama-server \</span><br><span class=line> -m Qwen3.8-27B-Q3_K_M.gguf \</span><br><span class=line> -ngl auto \</span><br><span class=line> -c 8192</span><br></pre></table></figure><p>具体能放多少层取决于 GGUF 大小、上下文长度、KV 精度、系统当前可用显存和 llama.cpp 版本。<code>-ngl auto</code> 比照搬别人机器上的固定层数更稳妥。<p>FreeToken 当前文档虽然支持 Qwen3.6-27B Dense,但 Dense 模型的 <code>auto</code> 会解析为 <code>fused</code>,也就是要求权重常驻 GPU,并不会启用 MoE offload/hybrid 这套专家机制。当前支持列表也还没有列出刚发布的 Qwen3.8-27B。<p>因此:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br><span class=line>3</span><br><span class=line>4</span><br><span class=line>5</span><br></pre><td class=code><pre><span class=line>8GB 4060 + Qwen3.6-35B-A3B MoE</span><br><span class=line>-> FreeToken 的专家缓存和 hybrid 很有意义</span><br><span class=line></span><br><span class=line>8GB 4060 + Qwen3.8-27B Dense</span><br><span class=line>-> 更适合 llama.cpp 的量化和静态层卸载</span><br></pre></table></figure><p>两个模型参数接近,但运行路径不能混着解释。<blockquote><p>如果你的显卡只有 6GB,我另外整理了 <a href=/2026/09/05/6GB%E6%98%BE%E5%AD%98%E8%B7%9135B-MoE-FreeToken%E6%9E%81%E9%99%90%E9%85%8D%E7%BD%AE%E4%B8%8E%E5%AE%9E%E6%93%8D%E6%95%99%E7%A8%8B/>6GB 显存运行 35B MoE 的极限配置与实操教程</a>。其中包含 RTX 3060 Laptop 6GB 的已知成功配置、内存下限、逐级 cache 调优和可下载实验脚本。6GB 不属于论文基准,性能结论需要和本文的 8GB 数据严格区分。</blockquote><h2 id=实际怎么跑-FreeToken><a title="实际怎么跑 FreeToken" class=headerlink href=#实际怎么跑-FreeToken></a>实际怎么跑 FreeToken</h2><p>当前开源 CLI 的要求是:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br><span class=line>3</span><br><span class=line>4</span><br><span class=line>5</span><br></pre><td class=code><pre><span class=line>Linux x86_64</span><br><span class=line>NVIDIA GPU</span><br><span class=line>driver r580+</span><br><span class=line>CUDA 13</span><br><span class=line>Python 3.10+</span><br></pre></table></figure><p>所以它目前不是 M3 Pro 上 llama.cpp/Metal 的替代品。Mac 仍然更适合 llama.cpp 或 MLX。<p>安装:<figure class="highlight bash"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br><span class=line>3</span><br></pre><td class=code><pre><span class=line>uv venv</span><br><span class=line><span class=built_in>source</span> .venv/bin/activate</span><br><span class=line>uv pip install <span class=string>"freetoken[accel]"</span></span><br></pre></table></figure><p>先测一次这台机器的 CPU/PCIe 带宽:<figure class="highlight bash"><table><tr><td class=gutter><pre><span class=line>1</span><br></pre><td class=code><pre><span class=line>ft bench bw --dtype nvfp4</span><br></pre></table></figure><p>然后启动论文中 8GB 机器使用的 NVFP4 模型:<figure class="highlight bash"><table><tr><td class=gutter><pre><span class=line>1</span><br></pre><td class=code><pre><span class=line>ft serve --model nvidia/Qwen3.6-35B-A3B-NVFP4</span><br></pre></table></figure><p>只提供 <code>--model</code> 也可以,dtype、attention backend、MoE backend、专家缓存和 KV 容量会根据 checkpoint、显存和带宽 profile 自动推导。<p>想明确观察模式,可以分别测试:<figure class="highlight bash"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br><span class=line>3</span><br><span class=line>4</span><br><span class=line>5</span><br><span class=line>6</span><br><span class=line>7</span><br><span class=line>8</span><br><span class=line>9</span><br><span class=line>10</span><br><span class=line>11</span><br><span class=line>12</span><br><span class=line>13</span><br><span class=line>14</span><br></pre><td class=code><pre><span class=line><span class=comment># 专家在内存,miss 全部搬到 GPU</span></span><br><span class=line>ft serve \</span><br><span class=line> --model nvidia/Qwen3.6-35B-A3B-NVFP4 \</span><br><span class=line> --moe-backend offload</span><br><span class=line></span><br><span class=line><span class=comment># 专家在内存,miss 由 CPU 处理</span></span><br><span class=line>ft serve \</span><br><span class=line> --model nvidia/Qwen3.6-35B-A3B-NVFP4 \</span><br><span class=line> --moe-backend cpu</span><br><span class=line></span><br><span class=line><span class=comment># GPU cache hit + PCIe fill + CPU overflow 并行</span></span><br><span class=line>ft serve \</span><br><span class=line> --model nvidia/Qwen3.6-35B-A3B-NVFP4 \</span><br><span class=line> --moe-backend hybrid</span><br></pre></table></figure><p>运行后可以查看:<figure class="highlight bash"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br></pre><td class=code><pre><span class=line>ft ctl stats</span><br><span class=line>ft ctl cache</span><br></pre></table></figure><p>对比时不要只测一句 <code>hello</code>。至少分别看:<ul><li>冷启动后的第一轮<li>长 Prompt 的 TTFT<li>连续生成几百 token 后的 Decode<li>多轮工具调用后是否反复 Prefill</ul><p>短 Prompt、冷缓存和长时间 Agent 会话测到的是三种不同状态。<h2 id=这套方案的限制><a class=headerlink href=#这套方案的限制 title=这套方案的限制></a>这套方案的限制</h2><p>看到 39.3 tok/s 很容易只记住“8GB 显存”,但这套方案还有几个前提。<h3 id=内存仍然要装得下完整专家池><a class=headerlink href=#内存仍然要装得下完整专家池 title=内存仍然要装得下完整专家池></a>内存仍然要装得下完整专家池</h3><p>显存需求降低不等于总内存需求消失。论文里的 4060 Laptop 有 32GiB 内存,并且使用 NVFP4。只有 16GB 系统内存时,同一模型可能会进入 swap,速度会急剧下降,甚至无法稳定加载。<h3 id=主要适用于-MoE><a title="主要适用于 MoE" class=headerlink href=#主要适用于-MoE></a>主要适用于 MoE</h3><p>Dense 模型每个 token 都要读完整 FFN,没有小而稳定的专家工作集。FreeToken 的核心专家缓存不会把 27B Dense 变成 3B active。<h3 id=性能受整机而不是只受-GPU-决定><a title="性能受整机而不是只受 GPU 决定" class=headerlink href=#性能受整机而不是只受-GPU-决定></a>性能受整机而不是只受 GPU 决定</h3><p>需要同时看:<ul><li>Host RAM 带宽<li>PCIe 代际和通道数<li>CPU SIMD 能力<li>物理核心数<li>显存能留下多少专家 slot<li>实际路由的局部性</ul><p>同一张 4060 放在不同笔记本里,内存和 PCIe 配置不同,<code>q*</code> 和速度也会不同。<h3 id=39-3-tok-s-不是所有请求的固定速度><a title="39.3 tok/s 不是所有请求的固定速度" class=headerlink href=#39-3-tok-s-不是所有请求的固定速度></a>39.3 tok/s 不是所有请求的固定速度</h3><p>这是论文在指定 4060 Laptop、NVFP4 模型和 coding-agent workload 下测得的 Decode 吞吐。它不等于:<ul><li>冷启动速度<li>Prefill 速度<li>超长上下文下的平均响应速度<li>任意量化、任意机器都能得到的速度</ul><h3 id=缓存也有冷启动和工作集突变><a class=headerlink href=#缓存也有冷启动和工作集突变 title=缓存也有冷启动和工作集突变></a>缓存也有冷启动和工作集突变</h3><p>刚启动时专家缓存是空的。如果话题、语言或者任务突然变化,路由热点也可能变化,短时间 miss 会增加。LRU 利用的是局部性,不是消灭所有 miss。<h2 id=最后总结><a class=headerlink href=#最后总结 title=最后总结></a>最后总结</h2><p>现在再看“8GB 显存运行 35B”,可以把它拆成五步:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br><span class=line>3</span><br><span class=line>4</span><br><span class=line>5</span><br></pre><td class=code><pre><span class=line>1. Qwen3.6-35B-A3B 是 MoE,每个 token 只激活约 3B</span><br><span class=line>2. 完整的量化专家池放在 32GiB Host RAM,不是硬塞进 8GB VRAM</span><br><span class=line>3. 剩余显存作为跨层共享的 LRU 专家 slot cache</span><br><span class=line>4. Decode miss 按 q* 分给 PCIe+GPU 和 CPU,两条路径并发并精确合并</span><br><span class=line>5. Prefill 改用完整层双缓冲,并通过语义状态缓存减少 Agent 重算</span><br></pre></table></figure><p>llama.cpp 的 <code>-ngl</code> 主要解决的是“完整层怎样静态分布到 CPU 和 GPU”,<code>--cpu-moe</code> 则进一步允许专家留在 CPU。FreeToken 在这个基础问题上继续往前走,把专家当成随 token 路由变化的动态工作集,并围绕它设计缓存、带宽分流、Prefill 和运行时显存管理。<p>所以 FreeToken 真正有意思的地方并不是一个神奇的“8GB 跑 35B”数字,而是它换了一个看待本地机器的方式:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br><span class=line>3</span><br></pre><td class=code><pre><span class=line>GPU 不是唯一计算设备</span><br><span class=line>显存也不是唯一模型内存</span><br><span class=line>CPU、RAM、PCIe 和 GPU 可以组成一台统一的推理机器</span><br></pre></table></figure><p>但这条路成立的关键仍然是 MoE 的稀疏性。换成 Qwen3.8-27B 这样的 Dense 模型,还是要老老实实回到量化、层卸载和内存带宽这几个问题上。<h2 id=参考资料><a class=headerlink href=#参考资料 title=参考资料></a>参考资料</h2><ul><li><a href=https://arxiv.org/abs/2608.16157 rel=noopener target=_blank>FreeToken 论文:Efficient Edge-Native MoE Serving with Bandwidth-Adaptive Execution</a><li><a href=https://github.com/FlashML-org/FreeToken rel=noopener target=_blank>FreeToken 源码</a><li><a href=https://github.com/FlashML-org/FreeToken/blob/main/docs/cli.md rel=noopener target=_blank>FreeToken CLI 文档</a><li><a href=https://huggingface.co/Qwen/Qwen3.6-35B-A3B rel=noopener target=_blank>Qwen3.6-35B-A3B 官方模型卡</a><li><a href=https://huggingface.co/Qwen/Qwen3.8-27B rel=noopener target=_blank>Qwen3.8-27B 官方模型卡</a><li><a href=https://github.com/ggml-org/llama.cpp/blob/master/tools/cli/README.md rel=noopener target=_blank>llama.cpp CLI 参数</a><li><a href=https://github.com/ggml-org/llama.cpp/issues/20757 rel=noopener target=_blank>llama.cpp persistent expert cache 讨论</a></ul></div><footer class=post-footer><aside aria-label=文章所属专题 class=post-series><span>所属专题</span><a href=/topics/#local-llm>低成本大模型推理</a><p>从本地部署、量化和 KV Cache 参数入手,逐步理解显存占用、GPU Offload 与 MoE 专家缓存。</aside><div class="affiliate-creative affiliate-creative--post-end" data-affiliate-id=tencent-cloud-server><a rel="sponsored noopener" aria-label=腾讯云服务器优惠 href=https://cloud.tencent.com/act/cps/redirect?redirect=6544&cps_key=2e33a1eedb380997d03618b4a5ba6ca7&from=console target=_blank><img alt="腾讯云 AI 焕新云服务器优惠" decoding=async height=241 loading=lazy src=/uploads/affiliate/tencent-cloud-server.webp width=690> </a><span class=affiliate-disclosure>推广</span></div><div class=reward-container><div>请我一杯咖啡吧!</div><button>赞赏</button><div class=post-reward><div><img alt="Nicksxs 微信" src=https://i.loli.net/2020/01/12/NUlWanT31E8LQGx.png><span>微信</span></div><div><img alt="Nicksxs 支付宝" src=https://ooo.0o0.ooo/2016/08/17/57b421e773057.jpg><span>支付宝</span></div></div></div><div class=post-copyright><ul><li class=post-copyright-author><strong>本文作者: </strong>Nicksxs<li class=post-copyright-link><strong>本文链接:</strong> <a href=https://nicksxs.me/2026/08/23/8GB%E6%98%BE%E5%AD%98%E5%A6%82%E4%BD%95%E8%B7%9135B-%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3FreeToken%E7%9A%84%E4%B8%93%E5%AE%B6%E7%BC%93%E5%AD%98%E4%B8%8ECPU-GPU%E5%8D%8F%E5%90%8C/ title=8GB显存如何跑35B:深入理解FreeToken的专家缓存与CPU-GPU协同>https://nicksxs.me/2026/08/23/8GB显存如何跑35B-深入理解FreeToken的专家缓存与CPU-GPU协同/</a><li class=post-copyright-license><strong>版权声明: </strong>本博客所有文章除特别声明外,均采用 <a href=https://creativecommons.org/licenses/by-nc-sa/4.0/ rel=noopener target=_blank><i class="fab fa-fw fa-creative-commons"></i>BY-NC-SA</a> 许可协议。转载请注明出处!</ul></div><div class=post-tags><a href=/tags/LLM/ rel=tag># LLM</a><a href=/tags/FreeToken/ rel=tag># FreeToken</a><a href=/tags/llama-cpp/ rel=tag># llama.cpp</a></div><div class=post-nav><div class=post-nav-item><a title="让chatgpt来给我讲解下deepseek harness的插件体系" href=/2026/08/16/%E8%AE%A9chatgpt%E6%9D%A5%E7%BB%99%E6%88%91%E8%AE%B2%E8%A7%A3%E4%B8%8Bdeepseek-harness%E7%9A%84%E6%8F%92%E4%BB%B6%E4%BD%93%E7%B3%BB/ rel=prev><i class="fa fa-angle-left"></i> 让chatgpt来给我讲解下deepseek harness的插件体系</a></div><div class=post-nav-item><a href=/2026/08/30/%E4%BB%8EASM%E5%88%B0ByteKit%E5%86%8D%E5%88%B0Arthas-%E4%B8%80%E6%9D%A1Java%E5%AD%97%E8%8A%82%E7%A0%81%E5%A2%9E%E5%BC%BA%E9%93%BE%E8%B7%AF/ rel=next title=从ASM到ByteKit再到Arthas:一条Java字节码增强链路>从ASM到ByteKit再到Arthas:一条Java字节码增强链路 <i class="fa fa-angle-right"></i></a></div></div></footer></article></div><div class=comments id=disqus_thread><noscript>Please enable JavaScript to view the comments powered by Disqus.</noscript></div></div></main><footer class=footer><div class=footer-inner><div class=beian><a href=https://beian.miit.gov.cn/ rel=noopener target=_blank>浙ICP备16002580号-2 </a><img alt src=/uploads/beian.png><a href=https://beian.mps.gov.cn/#/query/webSearch?code=33010602011094 rel=noopener target=_blank>浙公网安备 33010602011094号</a></div><div class=copyright>© <span itemprop=copyrightYear>2026</span><span class=with-love><i class="fa fa-heart"></i> </span><span class=author itemprop=copyrightHolder>Nicksxs</span></div></div></footer><div class="toggle sidebar-toggle" role=button><span class=toggle-line></span><span class=toggle-line></span><span class=toggle-line></span></div><div class=sidebar-dimmer></div><div aria-label=返回顶部 class=back-to-top role=button><i class="fa fa-arrow-up fa-lg"></i><span>0%</span></div><a aria-label="在 GitHub 上关注我" title="在 GitHub 上关注我" class=github-corner href=https://github.com/nicksxs rel=noopener target=_blank><svg viewbox="0 0 250 250" aria-hidden=true height=80 width=80><path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" style="transform-origin:130px 106px" class=octo-arm fill=currentColor></path><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" class=octo-body fill=currentColor></path></svg></a><noscript><div class=noscript-warning>Theme NexT works best with JavaScript enabled</div></noscript><script data-cloudflare-page-counter-config type=application/json>{"enable":true,"endpoint":"https://counter.nicksxs.me","site":"nicksxs","local_cache":true,"timeout":5000,"icon":"far fa-eye","item_text":true}</script><script src=/js/cloudflare-page-counter.js></script><script class=next-config data-name=disqus type=application/json>{"enable":true,"shortname":"nicksxs","count":false,"i18n":{"disqus":"disqus"}}</script><script defer src=/js/third-party/comments/disqus.js></script>