🔧 阿川の電商水電行
Shopify 顧問、維護與客製化
💡
小任務 / 單次支援方案
單次處理 Shopify 修正/微調
⭐️
維護方案
每月 Shopify 技術支援 + 小修改 + 諮詢
🚀
專案建置
Shopify 功能導入、培訓 + 分階段交付

Cursor 2.0 支持模型並發,我用國產 RWKV 模型實現了一模一樣的效果 🤩🤩🤩

最近 Cursor 發布 2.0 版本,其中一個比較亮點的功能就是它可以同時指揮 8 個 Agent 執行任務,最後選擇你覺得最好的那個答案。

而在底層模型層面,來自中國本土團隊的 RWKV 項目也帶來了更具突破性的成果:RWKV7-G0a3 13.3B ——當前全球最強的開源純 RNN 大語言模型。

這一版本以 RWKV6-world-v2.1 14B 為基礎,繼續訓練了 2 萬億 tokens(並融合了 35B 來自 DeepSeek v3.1 的高品質語料),在保持完全 RNN 架構、無注意力機制(No Attention)、無微調、無刷榜的前提下,取得了與主流 Transformer 模型相媲美甚至更優的表現。

20251104141030

在多項權威基準測試中(包括 MMLU、MMLU-Pro、GSM8K、MATH500、CEval 等),RWKV7-G0a3 在語言理解、邏輯推理與數學推演等任務上均實現顯著提升。其中,MMLU-Pro 測評顯示模型在多學科綜合知識上的掌握更加扎實;GSM8K 與 MATH500 結果表明,其在中高難度數學與邏輯問題上的推理能力已達到同規模模型的領先水平。與此同時,RWKV7-G0a3 繼續保持了 RWKV 系列一貫的高推理效率與低顯存占用優勢,展現出純 RNN 架構在大模型時代下的強大潛力。

Uncheatable Eval 使用最新的論文、新聞、代碼與小說等實時數據進行評測,通過“壓縮率”(即 Compression is Intelligence)指標,衡量模型在真實語料下的語言建模能力與泛化水平。

20251104141244

MMLU 系列用於測評語言模型在多學科知識與認知推理方面的能力,其中 MMLU Pro 為進階版本,包含更複雜的問題設計與更嚴苛的評測標準。

20251104141616

欲獲取更多詳細信息,請訪問該模型的 官方公眾號文章 閱讀。

這意味著:

在以 Transformer (deep learning architecture) 架構主導的大模型時代,RWKV 所代表的“純 RNN”路線再度崛起:以更低的計算與顯存成本、更自然的時序記憶機制,走出一條與主流 LLM 截然不同的進化路徑。

RWKV 命名規則中,G0a3 標識了訓練數據在版本與品質上的升級(例如:品質層級為 G# > G#a2 > G#,數據規模層級為 G1 > G0),即便參數量相同,G0a3 系列在泛化能力上也具備潛在優勢。綜合來看,RWKV7-G0a3 13.3B 的發布,不僅刷新了 RNN 模型性能的新高度,也象徵著 RWKV 系列在“擺脫 Transformer 架構壟斷”路徑上邁出了一步。

模型下載

下載 RWKV7-G0a3 13.3B 模型(.pth 格式):

下載 .gguf 格式: modelscope.cn/models/shou…

下載 Ollama 格式: ollama.com/mollysama

如何使用 RWKV 模型(本地部署)

可以使用 RWKV RunnerAi00rwkv pip 等推理工具在本地部署 RWKV 模型。

RWKV 模型同時兼容主流推理框架,如 llama.cppOllama

目前最快的 RWKV 推理工具是 Albatross

由於 RWKV7-G0a3 13.3B 屬於新模型,建議優先使用 RWKV Runner 以確保結果穩定與準確。

更多關於部署與推理的使用教程,可參考 RWKV 官網 - 模型部署和推理教程

前端如何實現模型並發的效果

首先,我們要知道模型並發的效果,那我們要知道連接了發起了一個請求之後,它是怎麼回覆的:

20251105181748

我們現在對的網絡請求已經進行了截取,這是其中的一些數據,我們將一下核心的數據寫到 json 文件裡面讓它能夠更好的展示:

20251105181910

首先我們知道這是一個流式返回,但是一次流式返回了包含的內容非常多,這裡就是我們並發的關鍵了,這裡的 index 代表並發的下標,而 delta.content 是具體的內容,這樣我們知道了 SSE 實現並發的原理了,實際上就是調用 SSE,後端在一次 SSE 的返回中返回同一個問題的不同的結果並通過下標來區分。

我們已經把 SSE 返回機制摸清楚了。下面就輕鬆地走一遍“邊生成邊展示”的整個流程:從流式到達、到何時更新、再到怎麼把半成品 HTML 安全地渲染出來,最後配上 UI 的滾動與分批加載。讀完你就能一眼看懂這套實時渲染是怎麼跑起來的。

先說結論:這件事其實就五步,順次串起來就好了——流式接收、增量累積與觸發、HTML 提取與補全、UI 局部更新、以及 iframe 的分批渲染。下面逐段拆開講。

一、流式數據接收(ai.ts:195–251)

// 使用 ReadableStream 讀取流式數據
const reader = response.body.getReader();
const decoder = new TextDecoder("utf-8");
let partial = ""; // 處理不完整的行

while (true) {
  const { done, value } = await reader.read();
  if (done) break;

  const chunk = decoder.decode(value, { stream: true });
  partial += chunk;

  // 逐行解析 SSE 格式:data: {...}
  const lines = partial.split("\n");
  partial = lines.pop() || ""; // 保留不完整的行

  for (const line of lines) {
    if (!line.startsWith("data: ")) continue;
    const json: StreamChunk = JSON.parse(data);
    // 處理每個 chunk...
  }
}

這裡的代碼的核心要點如下:

  • 逐行處理:SSE 數據按行到達,split('\n') 拆開,半截 JSON 用 partial 暫存。
  • 字符安全:TextDecoder(..., { stream: true }) 負責拼接多字節字符,避免中文或 emoji 被截斷。
  • 過濾噪聲:只解析 data: 開頭的有效行,忽略心跳、空行、註釋。
  • 流式收尾:遇到 [DONE] 僅結束對應流 index,其餘繼續處理。

二、增量累積與智能觸發(ai.ts:224–244)

// 為每個 index 累積內容
contentBuffers[index] += delta;

// 判斷是否應該觸發渲染
const lastLength = lastRenderedLength.get(index) || 0;
const shouldRender = this.shouldTriggerRender(
  contentBuffers[index],
  lastLength
);

if (shouldRender && onProgress) {
  const htmlCode = this.extractHTMLCode(contentBuffers[index]);
  onProgress(index, contentBuffers[index], htmlCode);
  lastRenderedLength.set(index, contentBuffers[index].length);
}

這裡的代碼的核心要點如下:

  • 多流並行:每個 index 各自獨立累積,互不干擾。
  • 智能觸發:通過與 lastRenderedLength 比較控制頻率,避免“來一點就刷”。
  • 精準更新:只觸發對應 index 的渲染,避免全局重排。
  • 兜底刷新:流結束後進行最終更新,確保結果完整。

三、觸發策略(ai.ts:62–101)

private static shouldTriggerRender(
  newContent: string,
  oldLength: number,
): boolean {
  // 1. 首次渲染:內容超過 20 字符
  if (oldLength === 0 && newLength > 20) {
    return true;
  }

  // 2. 關鍵閉合標籤出現(語義區塊完成)
  const keyClosingTags = [
    '</header>', '</section>', '</main>',
    '</article>', '</footer>', '</nav>',
    '</aside>', '</div>', '</body>', '</html>'
  ];

  const addedContent = newContent.substring(oldLength);
  for (const tag of keyClosingTags) {
    if (addedContent.includes(tag)) {
      return true; // 區塊完成,立即渲染
    }
  }

  // 3. 內容增長超過 200 字符(防止長時間不更新)
  if (newLength - oldLength > 200) {
    return true;
  }

  return false;
}

這裡的代碼的核心要點如下:

  • 首幀提速:內容首次超過 20 字符立即渲染,減少“首屏空白”。
  • 語義閉合優先:檢測新增片段中的關鍵閉合標籤(如 </section></div>),保證塊級內容完整展示。
  • 超長兜底:即使未閉合,增量超 200 字符也強制刷新。
  • 性能友好:僅比較“新增部分”,無需重複掃描舊文本;參數可根據模型節奏與設備性能調節。

四、HTML 提取與自動補全(ai.ts:19–59, 104–145)

private static extractHTMLCode(content: string): string {
  // 方式1: 完整的 ```html 代碼塊
  const codeBlockMatch = content.match(/```html\s*([\s\S]*?)```/);
  if (codeBlockMatch) return codeBlockMatch[1].trim();

  // 方式2: 未完成的代碼塊(流式渲染)
  const incompleteMatch = content.match(/```html\s*([\s\S]*?)$/);
  if (incompleteMatch) {
    return this.autoCompleteHTML(incompleteMatch[1].trim());
  }

  // 方式3: 直接以 <!DOCTYPE 或 <html 開頭
  if (trimmed.startsWith('<!DOCTYPE') || trimmed.startsWith('<html')) {
    return this.autoCompleteHTML(trimmed);
  }

  return '';
}

private static autoCompleteHTML(html: string): string {
  // 移除最後不完整的標籤(如 "<div cla")
  if (lastOpenBracket > lastCloseBracket) {
    result = html.substring(0, lastOpenBracket);
  }

  // 自動閉合 script、body、html 標籤
  // 確保瀏覽器可以渲染未完成的 HTML
  return result;
}

這裡的代碼的核心要點如下:

  • 多格式兼容:完整塊、未閉合塊、裸 HTML 均可識別。
  • 容錯補齊:遇到半截標籤(如 <div cla>)自動裁剪,再補上 </script></body></html> 等關鍵閉合。
  • 最小修正:僅做“可渲染”層面的修復,保持生成內容原貌。
  • 安全回退:提不出 HTML 時返回空字符串,避免將解釋性文字誤渲染。

五、UI 實時更新(ChatPage.tsx:240–253)

await AIService.generateMultipleResponses(
  userPrompt,
  totalCount,
  (index, content, htmlCode) => {
    // 實時更新對應 index 的結果
    setResults((prev) =>
      prev.map((result, i) =>
        i === index
          ? {
              ...result,
              content, // 原始 Markdown 內容
              htmlCode, // 提取的 HTML 代碼
              isLoading: false,
            }
          : result
      )
    );
  }
);

這裡的代碼的核心要點如下:

  • 局部更新:僅更新目標項,prev.map 保證不可變數據結構,減少重渲染。
  • 雙軌推進:content 用於 Markdown 文本,htmlCode 用於預覽展示。
  • 快速反饋:首批數據到達即撤骨架屏,讓用戶感知“正在生成”。
  • 狀態持久:結果存入 sessionStorage,刷新或返回依舊保留上下文。

六、iframe 分批渲染優化(ChatPage.tsx:109–175)

// 找出已準備好但還未渲染的索引
const readyIndexes = results
  .filter(({ result }) => !result.isLoading && result.htmlCode)
  .map(({ index }) => index)
  .sort((a, b) => a - b);

// 第一次渲染:一次性全部加載(用戶體驗優先)
if (!hasRenderedOnce.current) {
  setIframeRenderQueue(new Set(readyIndexes));
  hasRenderedOnce.current = true;
  return;
}

// 後續渲染:分批加載(每批 8 個,間隔 300ms)
// 避免一次性創建太多 iframe 導致卡頓
const processBatch = () => {
  const toAdd = stillNotInQueue.slice(0, batchSize); // 8 個
  toAdd.forEach((index) => newQueue.add(index));

  if (stillNotInQueue.length > batchSize) {
    setTimeout(processBatch, 300); // 繼續下一批
  }
};

這裡的代碼的核心要點如下:

  • 首批全放:初次渲染不延遲,保證響應速度。
  • 後續分批:按批次(默認 8 個/300ms)漸進掛載,防止主線程卡頓。
  • 動態調度:每輪重新計算“未入隊項”,保證不漏項。
  • 輕量 DOM:僅渲染必要 iframe,滾動與交互更順滑;參數可按性能靈活調整。

小結

通過語義閉合與字數閾值控制更新頻率讓畫面穩定流暢,HTML 半成品自動補齊避免黑屏,iframe 分批掛載減輕主線程壓力並配合 requestAnimationFrame 提升滾動順滑度,狀態由 sessionStorage 兜底並以日誌輔助調參;整體邏輯是流式接收邊累積、攢到關鍵點就渲染一幀、UI 精準更新該動的那格,調順節奏即可實現實時渲染的又快又穩。

效果展示

前面我們說了這麼多代碼相關的,接下來我們可以把我們的項目運行起來看一下最終運行的效果:

20251105190158

為了讓 UI 的效果顯示得更好,建議使用 33%縮放的螢幕效果。

20251105190242

在輸入框輸入我們要問的問題,點擊發送,你會看到這樣的效果:

20251105190307

這會你能實時看到 24 個頁面實時渲染的效果:

20251105190430

這樣我們就借助 RWKV7-G0a3 13.3B 模型實現了跟 Cursor2.0 版本一模一樣的效果了。

總結

RWKV7-G0a3 13.3B 是由中國團隊推出的最新一代純 RNN 大語言模型,在無 Attention 架構下實現了與主流 Transformer 模型相媲美的性能,並在多項基準測試中表現優異。它以更低顯存占用和高推理效率展示了 RNN 架構的強大潛力。而前端並發實現中,通過 SSE 流式返回不同 index 的內容,實現了同時生成多個模型響應的並行效果。結合智能觸發渲染與分批 iframe 更新,最終達成了類似 Cursor 2.0 的多 Agent 實時對比體驗。

前端倉庫地址

後端倉庫地址


原文出處:https://juejin.cn/post/7569044107391647779


精選技術文章翻譯,幫助開發者持續吸收新知。

共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。
🏆 本月排行榜
🥇
站長阿川
📝17   💬4   ❤️6
557
🥈
我愛JS
📝1   💬4   ❤️2
47
🥉
酷豪
1
#5
1
評分標準:發文×10 + 留言×3 + 獲讚×5 + 點讚×1 + 瀏覽數÷10
本數據每小時更新一次
🔧 阿川の電商水電行
Shopify 顧問、維護與客製化
💡
小任務 / 單次支援方案
單次處理 Shopify 修正/微調
⭐️
維護方案
每月 Shopify 技術支援 + 小修改 + 諮詢
🚀
專案建置
Shopify 功能導入、培訓 + 分階段交付