{
 "cells": [
  {
   "cell_type": "code",
   "id": "a1b2c3d4",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2026-08-27T11:37:11.922593400Z",
     "start_time": "2026-08-27T11:37:11.904044900Z"
    }
   },
   "source": [
    "from IPython.display import HTML, display\n"
   ],
   "outputs": [],
   "execution_count": 2
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "##  全元素 A/B/C Matmul 交互图\\n\\n切换矩阵标签页，查看当前 PID 和 K 阶段访问的每个 A、B、C 元素。图中同时显示矩阵坐标、row-major 线性地址、块范围、缩放和平移状态。\\n",
   "id": "mfp-full-elements"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2026-08-27T11:37:13.039039900Z",
     "start_time": "2026-08-27T11:37:13.006716400Z"
    }
   },
   "cell_type": "code",
   "source": [
    "matmul_full_process_html = r'''\n",
    "<style>\n",
    "#mfp-matrices [data-role=\"matrix-viewport\"] {\n",
    "  position: relative;\n",
    "  width: 100%;\n",
    "  height: 520px;\n",
    "  overflow: hidden;\n",
    "  border: 1px solid var(--border);\n",
    "  background: #ffffff;\n",
    "}\n",
    "#mfp-matrices canvas {\n",
    "  display: block;\n",
    "  width: 100%;\n",
    "  height: 100%;\n",
    "  cursor: grab;\n",
    "  touch-action: none;\n",
    "}\n",
    "#mfp-matrices canvas.is-dragging { cursor: grabbing; }\n",
    "#matmul-full-process .mfp-params {\n",
    "  display: grid;\n",
    "  grid-template-columns: repeat(auto-fit, minmax(118px, 1fr));\n",
    "  gap: 6px;\n",
    "  margin: 8px 0 10px;\n",
    "}\n",
    "#matmul-full-process .mfp-param {\n",
    "  display: flex;\n",
    "  flex-direction: column;\n",
    "  align-items: center;\n",
    "  justify-content: center;\n",
    "  padding: 6px 8px;\n",
    "  border: 1px solid var(--border);\n",
    "  background: #ffffff;\n",
    "  text-align: center;\n",
    "  min-width: 0;\n",
    "  overflow-wrap: anywhere;\n",
    "}\n",
    "#matmul-full-process .mfp-param-label {\n",
    "  color: var(--muted-foreground);\n",
    "}\n",
    "#matmul-full-process .mfp-param-value {\n",
    "  font-weight: 500;\n",
    "}\n",
    "#matmul-full-process .mfp-index-layout {\n",
    "  display: grid;\n",
    "  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);\n",
    "  gap: 12px;\n",
    "}\n",
    "#matmul-full-process .mfp-index-section {\n",
    "  min-width: 0;\n",
    "  padding-top: 8px;\n",
    "  border-top: 1px solid var(--border);\n",
    "}\n",
    "#matmul-full-process .mfp-index-section .text-small { overflow-wrap: anywhere; }\n",
    "#matmul-full-process .mfp-scroll {\n",
    "  max-width: 100%;\n",
    "  overflow-x: auto;\n",
    "  padding-bottom: 4px;\n",
    "}\n",
    "#matmul-full-process .mfp-array {\n",
    "  display: flex;\n",
    "  gap: 3px;\n",
    "  width: max-content;\n",
    "  margin: 4px 0 8px;\n",
    "}\n",
    "#matmul-full-process .mfp-value {\n",
    "  display: inline-flex;\n",
    "  align-items: center;\n",
    "  justify-content: center;\n",
    "  min-width: 27px;\n",
    "  min-height: 25px;\n",
    "  padding: 2px 4px;\n",
    "  box-sizing: border-box;\n",
    "  border: 1px solid var(--border);\n",
    "  background: #ffffff;\n",
    "  color: var(--foreground);\n",
    "  font-size: 11px;\n",
    "}\n",
    "#matmul-full-process .mfp-value.is-true { background: #dcfce7; color: #14532d; }\n",
    "#matmul-full-process .mfp-value.is-false { background: #fee2e2; color: #7f1d1d; }\n",
    "#matmul-full-process .mfp-address-table {\n",
    "  border-collapse: collapse;\n",
    "  width: max-content;\n",
    "  margin-top: 4px;\n",
    "  font-variant-numeric: tabular-nums;\n",
    "}\n",
    "#matmul-full-process .mfp-address-table th,\n",
    "#matmul-full-process .mfp-address-table td {\n",
    "  min-width: 31px;\n",
    "  height: 25px;\n",
    "  padding: 2px 4px;\n",
    "  border: 1px solid var(--border);\n",
    "  background: #ffffff;\n",
    "  color: var(--foreground);\n",
    "  text-align: center;\n",
    "  font-size: 11px;\n",
    "  font-weight: 400;\n",
    "  white-space: nowrap;\n",
    "}\n",
    "#matmul-full-process .mfp-address-table th {\n",
    "  background: #eef2f7;\n",
    "  color: var(--muted-foreground);\n",
    "}\n",
    "#matmul-full-process .mfp-address-table td.is-true { background: #dcfce7; color: #14532d; }\n",
    "#matmul-full-process .mfp-address-table td.is-false { background: #fee2e2; color: #7f1d1d; }\n",
    "#matmul-full-process .mfp-address-table td.is-masked {\n",
    "  background: repeating-linear-gradient(135deg, #fee2e2 0, #fee2e2 5px, #fff7f7 5px, #fff7f7 10px);\n",
    "  color: #7f1d1d;\n",
    "}\n",
    "@media (max-width: 520px) {\n",
    "  #mfp-matrices [data-role=\"matrix-viewport\"] { height: 420px; }\n",
    "  #matmul-full-process .mfp-index-layout { grid-template-columns: 1fr; }\n",
    "  #matmul-full-process .mfp-params { grid-template-columns: 1fr; }\n",
    "  #matmul-full-process [data-role=\"ranges\"] { width: 100%; text-align: left !important; }\n",
    "}\n",
    "</style>\n",
    "\n",
    "<div id=\"matmul-full-process\" style=\"width:100%;box-sizing:border-box;padding:16px;border:1px solid #cbd5e1;border-radius:8px;background:#f8fafc;color:#172033;color-scheme:light;font-family:'Noto Sans CJK SC','Microsoft YaHei',ui-sans-serif,system-ui,sans-serif;--foreground:#172033;--muted-foreground:#5b6475;--border:#c9d2df;--primary:#2563eb;--primary-foreground:#ffffff;--viz-series-1:#2563eb;--viz-series-2:#0891b2;--viz-series-3:#d97706;--muted:#e2e8f0;\">\n",
    "  <div class=\"viz-controls\" style=\"align-items:flex-end;margin-bottom:10px;\">\n",
    "    <label class=\"form-label\" style=\"min-width:210px;flex:1;\">\n",
    "      <span>PID <output data-role=\"pid-value\">0</output> / 19</span>\n",
    "      <input class=\"form-range\" data-role=\"pid\" type=\"range\" min=\"0\" max=\"19\" value=\"0\" step=\"1\">\n",
    "    </label>\n",
    "    <label class=\"form-label\" style=\"min-width:180px;flex:1;\">\n",
    "      <span>K 阶段 <output data-role=\"k-value\">0</output> / 2</span>\n",
    "      <input class=\"form-range\" data-role=\"k\" type=\"range\" min=\"0\" max=\"2\" value=\"0\" step=\"1\">\n",
    "    </label>\n",
    "    <button class=\"btn\" type=\"button\" data-action=\"previous\">上一步</button>\n",
    "    <button class=\"btn btn-primary\" type=\"button\" data-action=\"play\">播放</button>\n",
    "    <button class=\"btn\" type=\"button\" data-action=\"next\">下一步</button>\n",
    "  </div>\n",
    "  <div class=\"viz-row text-small\" aria-live=\"polite\" style=\"justify-content:space-between;gap:8px;margin-bottom:8px;\">\n",
    "    <span data-role=\"status\"></span>\n",
    "    <span data-role=\"ranges\" style=\"overflow-wrap:anywhere;text-align:right;\"></span>\n",
    "  </div>\n",
    "  <div class=\"mfp-params\" aria-label=\"当前矩阵和块参数\">\n",
    "    <span class=\"mfp-param\"><span class=\"mfp-param-label\">M</span><span class=\"mfp-param-value\">80</span></span>\n",
    "    <span class=\"mfp-param\"><span class=\"mfp-param-label\">N</span><span class=\"mfp-param-value\">80</span></span>\n",
    "    <span class=\"mfp-param\"><span class=\"mfp-param-label\">K</span><span class=\"mfp-param-value\">5</span></span>\n",
    "    <span class=\"mfp-param\"><span class=\"mfp-param-label\">BLOCK_SIZE_M</span><span class=\"mfp-param-value\">16</span></span>\n",
    "    <span class=\"mfp-param\"><span class=\"mfp-param-label\">BLOCK_SIZE_N</span><span class=\"mfp-param-value\">20</span></span>\n",
    "    <span class=\"mfp-param\"><span class=\"mfp-param-label\">BLOCK_SIZE_K</span><span class=\"mfp-param-value\">2</span></span>\n",
    "    <span class=\"mfp-param\"><span class=\"mfp-param-label\">GROUP_SIZE</span><span class=\"mfp-param-value\">2</span></span>\n",
    "  </div>\n",
    "\n",
    "  <div style=\"font-weight:500;margin:8px 0 3px;\">1. 一维 PID → 二维 PID 网格 → A、B、C 块网格</div>\n",
    "  <svg data-role=\"mapping\" viewBox=\"0 0 920 550\" role=\"img\" aria-labelledby=\"mfp-map-title mfp-map-desc\" style=\"display:block;width:100%;height:auto;\">\n",
    "    <title id=\"mfp-map-title\">一维 PID 到二维 PID 网格和 A、B、C 块的 group-major 映射</title>\n",
    "    <desc id=\"mfp-map-desc\">20 个一维 PID 先映射到五行四列的二维逻辑 PID 网格，再映射到 A、B、C 的块网格。</desc>\n",
    "  </svg>\n",
    "\n",
    "  <div style=\"font-weight:500;margin:12px 0 4px;\">2. 当前 PID/K 的 offsets、地址矩阵与 mask</div>\n",
    "  <div class=\"text-small\" data-role=\"index-summary\" aria-live=\"polite\" style=\"margin-bottom:8px;color:var(--muted-foreground);\"></div>\n",
    "  <div class=\"mfp-index-layout\">\n",
    "    <section class=\"mfp-index-section\">\n",
    "      <div style=\"font-weight:500;\">offset 向量</div>\n",
    "      <div data-role=\"offset-vectors\"></div>\n",
    "    </section>\n",
    "    <section class=\"mfp-index-section\">\n",
    "      <div style=\"font-weight:500;\">K 加载 mask</div>\n",
    "      <div data-role=\"load-mask\"></div>\n",
    "    </section>\n",
    "    <section class=\"mfp-index-section\">\n",
    "      <div style=\"font-weight:500;\">a_offsets：16×2</div>\n",
    "      <div class=\"text-small\" data-role=\"a-formula\" style=\"color:var(--muted-foreground);\"></div>\n",
    "      <div class=\"mfp-scroll\" data-role=\"a-offsets\"></div>\n",
    "    </section>\n",
    "    <section class=\"mfp-index-section\">\n",
    "      <div style=\"font-weight:500;\">b_offsets：2×20</div>\n",
    "      <div class=\"text-small\" data-role=\"b-formula\" style=\"color:var(--muted-foreground);\"></div>\n",
    "      <div class=\"mfp-scroll\" data-role=\"b-offsets\"></div>\n",
    "    </section>\n",
    "    <section class=\"mfp-index-section\" style=\"grid-column:1/-1;\">\n",
    "      <div style=\"font-weight:500;\">c_offsets：16×20</div>\n",
    "      <div class=\"text-small\" data-role=\"c-formula\" style=\"color:var(--muted-foreground);\"></div>\n",
    "      <div class=\"mfp-scroll\" data-role=\"c-offsets\"></div>\n",
    "    </section>\n",
    "    <section class=\"mfp-index-section\" style=\"grid-column:1/-1;\">\n",
    "      <div style=\"font-weight:500;\">c_mask：16×20</div>\n",
    "      <div class=\"text-small\" data-role=\"c-mask-formula\" style=\"color:var(--muted-foreground);\"></div>\n",
    "      <div class=\"mfp-scroll\" data-role=\"c-mask\"></div>\n",
    "    </section>\n",
    "  </div>\n",
    "\n",
    "  <div id=\"mfp-matrices\">\n",
    "    <div class=\"viz-row\" style=\"align-items:center;justify-content:space-between;margin:10px 0 5px;\">\n",
    "      <div class=\"viz-row\" role=\"tablist\" aria-label=\"矩阵选择\">\n",
    "        <button class=\"btn\" type=\"button\" role=\"tab\" aria-selected=\"true\" data-tab=\"A\">A (80×5)</button>\n",
    "        <button class=\"btn\" type=\"button\" role=\"tab\" aria-selected=\"false\" data-tab=\"B\">B (5×80)</button>\n",
    "        <button class=\"btn\" type=\"button\" role=\"tab\" aria-selected=\"false\" data-tab=\"C\">C (80×80)</button>\n",
    "      </div>\n",
    "      <div class=\"viz-row\">\n",
    "        <button class=\"btn\" type=\"button\" data-action=\"fit\">完整矩阵</button>\n",
    "        <button class=\"btn\" type=\"button\" data-action=\"locate\">定位当前块</button>\n",
    "      </div>\n",
    "    </div>\n",
    "    <div class=\"viz-row text-small\" style=\"justify-content:space-between;align-items:flex-start;gap:8px;margin-bottom:5px;\">\n",
    "      <span data-role=\"matrix-status\"></span>\n",
    "      <span data-role=\"matrix-stride\" style=\"overflow-wrap:anywhere;text-align:right;\"></span>\n",
    "    </div>\n",
    "    <div class=\"viz-row text-small\" style=\"justify-content:flex-end;margin-bottom:5px;\">\n",
    "      <label style=\"display:flex;align-items:center;gap:6px;\">\n",
    "        <span>缩放</span>\n",
    "        <input class=\"form-range\" data-role=\"zoom\" type=\"range\" min=\"0.1\" max=\"3\" value=\"1\" step=\"0.05\" style=\"width:150px;\">\n",
    "        <output data-role=\"zoom-value\">1.00×</output>\n",
    "      </label>\n",
    "    </div>\n",
    "    <div data-role=\"matrix-viewport\">\n",
    "      <canvas data-role=\"matrix-canvas\" aria-label=\"矩阵坐标和线性地址图\"></canvas>\n",
    "    </div>\n",
    "    <div class=\"viz-row text-small\" style=\"margin-top:6px;\">\n",
    "      <span><span aria-hidden=\"true\" style=\"display:inline-block;width:12px;height:12px;background:var(--primary);margin-right:5px;\"></span>当前 K 阶段</span>\n",
    "      <span><span aria-hidden=\"true\" style=\"display:inline-block;width:12px;height:12px;background:color-mix(in srgb,var(--viz-series-2) 25%,transparent);margin-right:5px;\"></span>同一 PID 的其他 K 阶段</span>\n",
    "      <span><span aria-hidden=\"true\" style=\"display:inline-block;width:12px;height:12px;background:color-mix(in srgb,var(--viz-series-3) 25%,transparent);margin-right:5px;\"></span>当前 PID 的 C 块</span>\n",
    "    </div>\n",
    "  </div>\n",
    "</div>\n",
    "\n",
    "<script>\n",
    "(() => {\n",
    "  const root = document.getElementById('matmul-full-process');\n",
    "  const pidInput = root.querySelector('[data-role=\"pid\"]');\n",
    "  const kInput = root.querySelector('[data-role=\"k\"]');\n",
    "  const pidValue = root.querySelector('[data-role=\"pid-value\"]');\n",
    "  const kValue = root.querySelector('[data-role=\"k-value\"]');\n",
    "  const status = root.querySelector('[data-role=\"status\"]');\n",
    "  const ranges = root.querySelector('[data-role=\"ranges\"]');\n",
    "  const mappingSvg = root.querySelector('[data-role=\"mapping\"]');\n",
    "  const indexSummary = root.querySelector('[data-role=\"index-summary\"]');\n",
    "  const offsetVectors = root.querySelector('[data-role=\"offset-vectors\"]');\n",
    "  const loadMask = root.querySelector('[data-role=\"load-mask\"]');\n",
    "  const aFormula = root.querySelector('[data-role=\"a-formula\"]');\n",
    "  const bFormula = root.querySelector('[data-role=\"b-formula\"]');\n",
    "  const cFormula = root.querySelector('[data-role=\"c-formula\"]');\n",
    "  const cMaskFormula = root.querySelector('[data-role=\"c-mask-formula\"]');\n",
    "  const aOffsets = root.querySelector('[data-role=\"a-offsets\"]');\n",
    "  const bOffsets = root.querySelector('[data-role=\"b-offsets\"]');\n",
    "  const cOffsets = root.querySelector('[data-role=\"c-offsets\"]');\n",
    "  const cMask = root.querySelector('[data-role=\"c-mask\"]');\n",
    "  const matrixStatus = root.querySelector('[data-role=\"matrix-status\"]');\n",
    "  const matrixStride = root.querySelector('[data-role=\"matrix-stride\"]');\n",
    "  const canvas = root.querySelector('[data-role=\"matrix-canvas\"]');\n",
    "  const viewport = root.querySelector('[data-role=\"matrix-viewport\"]');\n",
    "  const zoomInput = root.querySelector('[data-role=\"zoom\"]');\n",
    "  const zoomValue = root.querySelector('[data-role=\"zoom-value\"]');\n",
    "  const previous = root.querySelector('[data-action=\"previous\"]');\n",
    "  const next = root.querySelector('[data-action=\"next\"]');\n",
    "  const play = root.querySelector('[data-action=\"play\"]');\n",
    "  const tabButtons = Array.from(root.querySelectorAll('[data-tab]'));\n",
    "  const ctx = canvas.getContext('2d');\n",
    "  const ns = 'http://www.w3.org/2000/svg';\n",
    "  const cfg = {M:80, N:80, K:5, BM:16, BN:20, BK:2, GROUP_SIZE:2};\n",
    "  const numM = 5, numN = 4, totalPids = 20, CELL = 30;\n",
    "  const matrices = {\n",
    "    A: {rows:80, cols:5, label:'A', stride:'stride_a_M=5 · stride_a_K=1', states:{scale:1, x:0, y:0, initialized:false}},\n",
    "    B: {rows:5, cols:80, label:'B', stride:'stride_b_K=80 · stride_b_N=1', states:{scale:1, x:0, y:0, initialized:false}},\n",
    "    C: {rows:80, cols:80, label:'C', stride:'stride_c_M=80 · stride_c_N=1', states:{scale:1, x:0, y:0, initialized:false}}\n",
    "  };\n",
    "  let activeMatrix = 'A', timer = null, dragging = false, dragStart = null;\n",
    "\n",
    "  function mapPid(pid) {\n",
    "    const groupWidth = cfg.GROUP_SIZE * numN;\n",
    "    const groupId = Math.floor(pid / groupWidth);\n",
    "    const firstM = groupId * cfg.GROUP_SIZE;\n",
    "    const groupSizeAdj = Math.min(numM - firstM, cfg.GROUP_SIZE);\n",
    "    const localPid = pid % groupWidth;\n",
    "    const pidM = firstM + (localPid % groupSizeAdj);\n",
    "    const pidN = Math.floor(localPid / groupSizeAdj);\n",
    "    return {pid, groupId, groupSizeAdj, localPid, pidM, pidN};\n",
    "  }\n",
    "  const mappings = Array.from({length:totalPids}, (_, pid) => mapPid(pid));\n",
    "\n",
    "  function el(name, attrs = {}, content = '') {\n",
    "    const item = document.createElementNS(ns, name);\n",
    "    Object.entries(attrs).forEach(([key, value]) => item.setAttribute(key, String(value)));\n",
    "    if (content !== '') item.textContent = content;\n",
    "    return item;\n",
    "  }\n",
    "  function label(svg, x, y, content, attrs = {}) {\n",
    "    svg.appendChild(el('text', {x, y, fill:'var(--foreground)', 'font-size':13, 'text-anchor':'middle', 'dominant-baseline':'middle', ...attrs}, content));\n",
    "  }\n",
    "  function rect(svg, x, y, width, height, attrs = {}) {\n",
    "    svg.appendChild(el('rect', {x, y, width, height, rx:2, fill:'transparent', stroke:'var(--border)', ...attrs}));\n",
    "  }\n",
    "  function addArrowMarker(svg, id, color) {\n",
    "    const defs = el('defs');\n",
    "    const marker = el('marker', {id, viewBox:'0 0 10 10', refX:9, refY:5, markerWidth:6, markerHeight:6, orient:'auto'});\n",
    "    marker.appendChild(el('path', {d:'M 0 0 L 10 5 L 0 10 z', fill:color}));\n",
    "    defs.appendChild(marker);\n",
    "    svg.appendChild(defs);\n",
    "  }\n",
    "  function arrow(svg, path, color = 'var(--viz-series-1)', marker = 'mfp-arrow-main') {\n",
    "    svg.appendChild(el('path', {d:path, fill:'none', stroke:'#ffffff', 'stroke-width':6, 'stroke-linejoin':'round'}));\n",
    "    svg.appendChild(el('path', {d:path, fill:'none', stroke:color, 'stroke-width':2, 'stroke-linejoin':'round', 'marker-end':`url(#${marker})`}));\n",
    "  }\n",
    "  function blockGrid(svg, x, y, rows, cols, selectedRow, selectedCol, cellW, cellH, prefix) {\n",
    "    for (let row = 0; row < rows; row += 1) {\n",
    "      for (let col = 0; col < cols; col += 1) {\n",
    "        const selected = row === selectedRow && col === selectedCol;\n",
    "        rect(svg, x + col * cellW, y + row * cellH, cellW - 4, cellH - 4, {\n",
    "          fill:selected ? 'var(--primary)' : (row === rows - 1 ? 'color-mix(in srgb,var(--viz-series-3) 26%,transparent)' : 'color-mix(in srgb,var(--viz-series-2) 16%,transparent)'),\n",
    "          stroke:selected ? 'var(--primary)' : 'var(--border)'\n",
    "        });\n",
    "        label(svg, x + col * cellW + (cellW - 4) / 2, y + row * cellH + (cellH - 4) / 2, `${prefix}[${row},${col}]`, {\n",
    "          fill:selected ? 'var(--primary-foreground)' : 'var(--foreground)', 'font-size':11, 'font-weight':selected ? 500 : 400\n",
    "        });\n",
    "      }\n",
    "    }\n",
    "  }\n",
    "  function twoLineLabel(svg, x, y, first, second, attrs = {}) {\n",
    "    const text = el('text', {x, y:y - 5, fill:'var(--foreground)', 'font-size':11, 'text-anchor':'middle', ...attrs});\n",
    "    text.appendChild(el('tspan', {x, dy:0}, first));\n",
    "    text.appendChild(el('tspan', {x, dy:12}, second));\n",
    "    svg.appendChild(text);\n",
    "  }\n",
    "  function renderMapping(selected) {\n",
    "    mappingSvg.replaceChildren();\n",
    "    addArrowMarker(mappingSvg, 'mfp-arrow-main', 'var(--viz-series-1)');\n",
    "    addArrowMarker(mappingSvg, 'mfp-arrow-a', 'var(--viz-series-1)');\n",
    "    addArrowMarker(mappingSvg, 'mfp-arrow-b', 'var(--viz-series-2)');\n",
    "    addArrowMarker(mappingSvg, 'mfp-arrow-c', 'var(--viz-series-3)');\n",
    "    mappingSvg.appendChild(el('rect', {x:0, y:0, width:920, height:550, fill:'#ffffff', stroke:'var(--border)', 'stroke-width':1}));\n",
    "\n",
    "    const stripX = 20, stripY = 40, cellW = 42;\n",
    "    const pidGridX = 360, pidGridY = 155, pidCellW = 50, pidCellH = 32;\n",
    "    const aX = 35, bX = 335, cX = 655, gridY = 390;\n",
    "    const sourceX = stripX + selected.pid * cellW + 18;\n",
    "    const pidTargetX = pidGridX + selected.pidN * pidCellW + 23;\n",
    "    const pidTargetY = pidGridY + selected.pidM * pidCellH + 14;\n",
    "    const aTargetX = aX + Number(kInput.value) * 60 + 28;\n",
    "    const bTargetX = bX + selected.pidN * 60 + 28;\n",
    "    const cTargetX = cX + selected.pidN * 60 + 28;\n",
    "    const aTargetY = gridY + selected.pidM * 24 + 10;\n",
    "    const bTargetY = gridY + Number(kInput.value) * 24 + 10;\n",
    "    const cTargetY = gridY + selected.pidM * 24 + 10;\n",
    "\n",
    "    // 长连接线先绘制，随后由网格覆盖穿越部分；目标单元内再绘制短箭头。\n",
    "    arrow(mappingSvg, `M ${sourceX} 70 V 112 H 600 V ${pidTargetY} H ${pidTargetX}`);\n",
    "    arrow(mappingSvg, `M ${pidTargetX - 12} ${pidTargetY} V 330 H 24 V ${aTargetY} H ${aTargetX}`, 'var(--viz-series-1)', 'mfp-arrow-a');\n",
    "    arrow(mappingSvg, `M ${pidTargetX} ${pidTargetY} V 340 H 324 V ${bTargetY} H ${bTargetX}`, 'var(--viz-series-2)', 'mfp-arrow-b');\n",
    "    arrow(mappingSvg, `M ${pidTargetX + 12} ${pidTargetY} V 350 H 908 V ${cTargetY} H ${cTargetX}`, 'var(--viz-series-3)', 'mfp-arrow-c');\n",
    "\n",
    "    label(mappingSvg, 20, 20, '一维启动网格：20 个程序实例', {'text-anchor':'start', fill:'var(--muted-foreground)'});\n",
    "    mappings.forEach(item => {\n",
    "      const active = item.pid === selected.pid;\n",
    "      rect(mappingSvg, stripX + item.pid * cellW, stripY, cellW - 3, 30, {\n",
    "        fill:active ? 'var(--primary)' : (item.groupId === 2 ? 'color-mix(in srgb,var(--viz-series-3) 30%,transparent)' : 'color-mix(in srgb,var(--viz-series-2) 16%,transparent)'),\n",
    "        stroke:active ? 'var(--primary)' : 'var(--border)'\n",
    "      });\n",
    "      label(mappingSvg, stripX + item.pid * cellW + (cellW - 3) / 2, stripY + 15, String(item.pid), {\n",
    "        fill:active ? 'var(--primary-foreground)' : 'var(--foreground)', 'font-size':11, 'font-weight':active ? 500 : 400\n",
    "      });\n",
    "    });\n",
    "    [0,8,16].forEach((start, group) => {\n",
    "      const count = group < 2 ? 8 : 4;\n",
    "      const x1 = stripX + start * cellW, x2 = x1 + count * cellW - 3;\n",
    "      mappingSvg.appendChild(el('path', {d:`M ${x1} 75 V 81 H ${x2} V 75`, fill:'none', stroke:group === 2 ? 'var(--viz-series-3)' : 'var(--muted-foreground)'}));\n",
    "      label(mappingSvg, (x1 + x2) / 2, 94, `group ${group}`, {'font-size':11, fill:group === 2 ? 'var(--viz-series-3)' : 'var(--muted-foreground)'});\n",
    "    });\n",
    "    label(mappingSvg, pidGridX + 100, 130, '二维逻辑 PID 网格（PID_M × PID_N = 5×4）', {'font-weight':500});\n",
    "    for (let row = 0; row < numM; row += 1) {\n",
    "      for (let col = 0; col < numN; col += 1) {\n",
    "        const item = mappings.find(entry => entry.pidM === row && entry.pidN === col);\n",
    "        const active = item.pid === selected.pid;\n",
    "        rect(mappingSvg, pidGridX + col * pidCellW, pidGridY + row * pidCellH, pidCellW - 4, pidCellH - 4, {\n",
    "          fill:active ? 'var(--primary)' : (item.groupId === 2 ? 'color-mix(in srgb,var(--viz-series-3) 28%,transparent)' : 'color-mix(in srgb,var(--viz-series-2) 16%,transparent)'),\n",
    "          stroke:active ? 'var(--primary)' : 'var(--border)'\n",
    "        });\n",
    "        twoLineLabel(mappingSvg, pidGridX + col * pidCellW + 23, pidGridY + row * pidCellH + 14, `PID ${item.pid}`, `(${row},${col})`, {\n",
    "          fill:active ? 'var(--primary-foreground)' : 'var(--foreground)', 'font-weight':active ? 500 : 400\n",
    "        });\n",
    "      }\n",
    "    }\n",
    "    label(mappingSvg, 20, 140, `PID ${selected.pid} 的映射计算`, {'text-anchor':'start', 'font-weight':500});\n",
    "    label(mappingSvg, 20, 165, `group_id = ${selected.groupId}`, {'text-anchor':'start', 'font-size':12});\n",
    "    label(mappingSvg, 20, 187, `local_PID = ${selected.localPid}`, {'text-anchor':'start', 'font-size':12});\n",
    "    label(mappingSvg, 20, 209, `group_size_adj = ${selected.groupSizeAdj}`, {'text-anchor':'start', 'font-size':12});\n",
    "    label(mappingSvg, 20, 231, `PID_M = ${selected.pidM}`, {'text-anchor':'start', 'font-size':12});\n",
    "    label(mappingSvg, 20, 253, `PID_N = ${selected.pidN}`, {'text-anchor':'start', 'font-size':12});\n",
    "    label(mappingSvg, aX + 90, 356, 'A 块网格：5×3，每块 16×2', {'font-weight':500});\n",
    "    label(mappingSvg, bX + 120, 356, 'B 块网格：3×4，每块 2×20', {'font-weight':500});\n",
    "    label(mappingSvg, cX + 120, 356, 'C 块网格：5×4，每块 16×20', {'font-weight':500});\n",
    "    blockGrid(mappingSvg, aX, gridY, 5, 3, selected.pidM, Number(kInput.value), 60, 24, 'A');\n",
    "    blockGrid(mappingSvg, bX, gridY, 3, 4, Number(kInput.value), selected.pidN, 60, 24, 'B');\n",
    "    blockGrid(mappingSvg, cX, gridY, 5, 4, selected.pidM, selected.pidN, 60, 24, 'C');\n",
    "    arrow(mappingSvg, `M ${pidGridX + selected.pidN * pidCellW + 46} ${pidTargetY} H ${pidTargetX}`);\n",
    "    arrow(mappingSvg, `M ${aX + Number(kInput.value) * 60} ${aTargetY} H ${aTargetX}`, 'var(--viz-series-1)', 'mfp-arrow-a');\n",
    "    arrow(mappingSvg, `M ${bX + selected.pidN * 60} ${bTargetY} H ${bTargetX}`, 'var(--viz-series-2)', 'mfp-arrow-b');\n",
    "    arrow(mappingSvg, `M ${cX + selected.pidN * 60 + 56} ${cTargetY} H ${cTargetX}`, 'var(--viz-series-3)', 'mfp-arrow-c');\n",
    "    // 将选中单元标签置于箭头之上，避免箭头遮挡文字。\n",
    "    twoLineLabel(mappingSvg, pidTargetX, pidTargetY, `PID ${selected.pid}`, `(${selected.pidM},${selected.pidN})`, {fill:'var(--primary-foreground)', 'font-weight':500});\n",
    "    label(mappingSvg, aTargetX, aTargetY, `A[${selected.pidM},${Number(kInput.value)}]`, {fill:'var(--primary-foreground)', 'font-size':11, 'font-weight':500});\n",
    "    label(mappingSvg, bTargetX, bTargetY, `B[${Number(kInput.value)},${selected.pidN}]`, {fill:'var(--primary-foreground)', 'font-size':11, 'font-weight':500});\n",
    "    label(mappingSvg, cTargetX, cTargetY, `C[${selected.pidM},${selected.pidN}]`, {fill:'var(--primary-foreground)', 'font-size':11, 'font-weight':500});\n",
    "    label(mappingSvg, 20, 523, `当前块：A[${selected.pidM},${Number(kInput.value)}] · B[${Number(kInput.value)},${selected.pidN}] · C[${selected.pidM},${selected.pidN}]`, {'text-anchor':'start', 'font-size':12, 'font-weight':500});\n",
    "    const kEnd = Math.min(cfg.K, (Number(kInput.value) + 1) * cfg.BK);\n",
    "    label(mappingSvg, 20, 541, `有效范围：A[${selected.pidM * cfg.BM}:${(selected.pidM + 1) * cfg.BM},${Number(kInput.value) * cfg.BK}:${kEnd}] · B[${Number(kInput.value) * cfg.BK}:${kEnd},${selected.pidN * cfg.BN}:${(selected.pidN + 1) * cfg.BN}] · C[${selected.pidM * cfg.BM}:${(selected.pidM + 1) * cfg.BM},${selected.pidN * cfg.BN}:${(selected.pidN + 1) * cfg.BN}]`, {'text-anchor':'start', 'font-size':10, fill:'var(--muted-foreground)'});\n",
    "  }\n",
    "\n",
    "  function makeHtml(name, className = '', content = '') {\n",
    "    const item = document.createElement(name);\n",
    "    if (className) item.className = className;\n",
    "    if (content !== '') item.textContent = content;\n",
    "    return item;\n",
    "  }\n",
    "  function appendArray(container, name, values, booleanValues = false) {\n",
    "    const labelNode = makeHtml('div', 'text-small', `${name}（${values.length} 项）`);\n",
    "    labelNode.style.color = 'var(--muted-foreground)';\n",
    "    const scroll = makeHtml('div', 'mfp-scroll');\n",
    "    const row = makeHtml('div', 'mfp-array');\n",
    "    values.forEach(value => {\n",
    "      const cell = makeHtml('span', 'mfp-value', booleanValues ? (value ? 'True' : 'False') : String(value));\n",
    "      if (booleanValues) cell.classList.add(value ? 'is-true' : 'is-false');\n",
    "      row.appendChild(cell);\n",
    "    });\n",
    "    scroll.appendChild(row);\n",
    "    container.append(labelNode, scroll);\n",
    "  }\n",
    "  function renderTable(container, values, rowLabels, colLabels, booleanValues = false, validity = null) {\n",
    "    container.replaceChildren();\n",
    "    const table = makeHtml('table', 'mfp-address-table');\n",
    "    const head = makeHtml('thead');\n",
    "    const headRow = makeHtml('tr');\n",
    "    headRow.appendChild(makeHtml('th', '', ''));\n",
    "    colLabels.forEach(value => headRow.appendChild(makeHtml('th', '', value)));\n",
    "    head.appendChild(headRow);\n",
    "    table.appendChild(head);\n",
    "    const body = makeHtml('tbody');\n",
    "    values.forEach((row, rowIndex) => {\n",
    "      const tableRow = makeHtml('tr');\n",
    "      tableRow.appendChild(makeHtml('th', '', rowLabels[rowIndex]));\n",
    "      row.forEach((value, colIndex) => {\n",
    "        const valid = !validity || validity[rowIndex][colIndex];\n",
    "        const content = booleanValues ? (value ? 'T' : 'F') : `${value}${valid ? '' : ' ×'}`;\n",
    "        const cell = makeHtml('td', '', content);\n",
    "        if (booleanValues) cell.classList.add(value ? 'is-true' : 'is-false');\n",
    "        if (!valid) {\n",
    "          cell.classList.add('is-masked');\n",
    "          cell.setAttribute('aria-label', `${value}，已被 mask 屏蔽`);\n",
    "        }\n",
    "        tableRow.appendChild(cell);\n",
    "      });\n",
    "      body.appendChild(tableRow);\n",
    "    });\n",
    "    table.appendChild(body);\n",
    "    container.appendChild(table);\n",
    "  }\n",
    "  function sequence(start, length) {\n",
    "    return Array.from({length}, (_, index) => start + index);\n",
    "  }\n",
    "  function renderIndexDetails(selected, k) {\n",
    "    const offsetsM = sequence(selected.pidM * cfg.BM, cfg.BM);\n",
    "    const offsetsN = sequence(selected.pidN * cfg.BN, cfg.BN);\n",
    "    const offsetsK = sequence(0, cfg.BK);\n",
    "    const globalK = offsetsK.map(value => value + k * cfg.BK);\n",
    "    const loadMaskValues = offsetsK.map(value => value < cfg.K - k * cfg.BK);\n",
    "    const aOffsetValues = offsetsM.map(row => globalK.map(col => row * cfg.K + col));\n",
    "    const bOffsetValues = globalK.map(row => offsetsN.map(col => row * cfg.N + col));\n",
    "    const cOffsetValues = offsetsM.map(row => offsetsN.map(col => row * cfg.N + col));\n",
    "    const cMaskValues = offsetsM.map(row => offsetsN.map(col => row < cfg.M && col < cfg.N));\n",
    "    const aValidity = offsetsM.map(() => loadMaskValues.slice());\n",
    "    const bValidity = loadMaskValues.map(valid => offsetsN.map(() => valid));\n",
    "\n",
    "    const maskedCount = loadMaskValues.filter(value => !value).length;\n",
    "    indexSummary.textContent = `PID ${selected.pid} / K${k}：a_offsets 已推进 ${k * cfg.BK}，b_offsets 已推进 ${k * cfg.BK * 80}；加载 mask 屏蔽 ${maskedCount} 个 K 位置，c_mask 的 320 个位置全部为 True。`;\n",
    "    offsetVectors.replaceChildren();\n",
    "    appendArray(offsetVectors, 'offsets_M', offsetsM);\n",
    "    appendArray(offsetVectors, 'offsets_N', offsetsN);\n",
    "    appendArray(offsetVectors, 'offsets_K（内核基础向量）', offsetsK);\n",
    "    appendArray(offsetVectors, `当前全局 K = offsets_K + ${k}×BLOCK_SIZE_K`, globalK);\n",
    "\n",
    "    loadMask.replaceChildren();\n",
    "    const maskFormula = makeHtml('div', 'text-small', `mask = offsets_K < K - k×BLOCK_SIZE_K = [0,1] < ${cfg.K - k * cfg.BK}`);\n",
    "    maskFormula.style.color = 'var(--muted-foreground)';\n",
    "    loadMask.appendChild(maskFormula);\n",
    "    appendArray(loadMask, 'mask', loadMaskValues, true);\n",
    "    appendArray(loadMask, 'A 使用 mask[None, :]', loadMaskValues, true);\n",
    "    appendArray(loadMask, 'B 使用 mask[:, None]', loadMaskValues, true);\n",
    "\n",
    "    aFormula.textContent = `a_offsets = offsets_M[:,None]×stride_a_M + 当前全局K[None,:]×stride_a_K；stride_a_M=5，stride_a_K=1；× 表示该地址不会被加载`;\n",
    "    bFormula.textContent = `b_offsets = 当前全局K[:,None]×stride_b_K + offsets_N[None,:]×stride_b_N；stride_b_K=80，stride_b_N=1`;\n",
    "    cFormula.textContent = 'c_offsets = offsets_M[:,None]×stride_c_M + offsets_N[None,:]×stride_c_N；stride_c_M=80，stride_c_N=1';\n",
    "    cMaskFormula.textContent = 'c_mask = (offsets_M[:,None] < M) & (offsets_N[None,:] < N)；T=True，F=False';\n",
    "    renderTable(aOffsets, aOffsetValues, offsetsM.map(value => `M${value}`), globalK.map(value => `K${value}`), false, aValidity);\n",
    "    renderTable(bOffsets, bOffsetValues, globalK.map(value => `K${value}`), offsetsN.map(value => `N${value}`), false, bValidity);\n",
    "    renderTable(cOffsets, cOffsetValues, offsetsM.map(value => `M${value}`), offsetsN.map(value => `N${value}`));\n",
    "    renderTable(cMask, cMaskValues, offsetsM.map(value => `M${value}`), offsetsN.map(value => `N${value}`), true);\n",
    "  }\n",
    "\n",
    "  function getHighlight(name, row, col, pid, k) {\n",
    "    const item = mappings[pid];\n",
    "    if (name === 'C') return row >= item.pidM * cfg.BM && row < (item.pidM + 1) * cfg.BM && col >= item.pidN * cfg.BN && col < (item.pidN + 1) * cfg.BN ? 'strong-c' : '';\n",
    "    if (name === 'A') {\n",
    "      if (row < item.pidM * cfg.BM || row >= (item.pidM + 1) * cfg.BM) return '';\n",
    "      return Math.floor(col / cfg.BK) === k ? 'strong' : 'weak';\n",
    "    }\n",
    "    if (name === 'B') {\n",
    "      if (col < item.pidN * cfg.BN || col >= (item.pidN + 1) * cfg.BN) return '';\n",
    "      return Math.floor(row / cfg.BK) === k ? 'strong' : 'weak';\n",
    "    }\n",
    "    return '';\n",
    "  }\n",
    "  function address(name, row, col) {\n",
    "    if (name === 'A') return row * cfg.K + col;\n",
    "    return row * cfg.N + col;\n",
    "  }\n",
    "  function fitView(name) {\n",
    "    const state = matrices[name].states;\n",
    "    const bounds = viewport.getBoundingClientRect();\n",
    "    const worldW = matrices[name].cols * CELL, worldH = matrices[name].rows * CELL;\n",
    "    state.scale = Math.max(0.1, Math.min(3, Math.min((bounds.width - 20) / worldW, (bounds.height - 20) / worldH)));\n",
    "    state.x = (bounds.width - worldW * state.scale) / 2;\n",
    "    state.y = (bounds.height - worldH * state.scale) / 2;\n",
    "    state.initialized = true;\n",
    "    renderMatrix();\n",
    "  }\n",
    "  function locateView(name) {\n",
    "    const state = matrices[name].states, bounds = viewport.getBoundingClientRect(), item = mappings[Number(pidInput.value)], k = Number(kInput.value);\n",
    "    let rowStart, rowCount, colStart, colCount;\n",
    "    if (name === 'A') { rowStart = item.pidM * cfg.BM; rowCount = cfg.BM; colStart = k * cfg.BK; colCount = Math.min(cfg.BK, cfg.K - colStart); }\n",
    "    else if (name === 'B') { rowStart = k * cfg.BK; rowCount = Math.min(cfg.BK, cfg.K - rowStart); colStart = item.pidN * cfg.BN; colCount = cfg.BN; }\n",
    "    else { rowStart = item.pidM * cfg.BM; rowCount = cfg.BM; colStart = item.pidN * cfg.BN; colCount = cfg.BN; }\n",
    "    const targetScale = name === 'A' ? 1.45 : name === 'B' ? 0.9 : 0.72;\n",
    "    state.scale = targetScale;\n",
    "    state.x = bounds.width / 2 - (colStart + colCount / 2) * CELL * state.scale;\n",
    "    state.y = bounds.height / 2 - (rowStart + rowCount / 2) * CELL * state.scale;\n",
    "    state.initialized = true;\n",
    "    renderMatrix();\n",
    "  }\n",
    "  function drawMatrix() {\n",
    "    const matrix = matrices[activeMatrix], state = matrix.states, bounds = viewport.getBoundingClientRect(), dpr = window.devicePixelRatio || 1;\n",
    "    canvas.width = Math.max(1, Math.round(bounds.width * dpr));\n",
    "    canvas.height = Math.max(1, Math.round(bounds.height * dpr));\n",
    "    ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n",
    "    ctx.clearRect(0, 0, bounds.width, bounds.height);\n",
    "    ctx.save();\n",
    "    ctx.translate(state.x, state.y);\n",
    "    ctx.scale(state.scale, state.scale);\n",
    "    ctx.textAlign = 'center';\n",
    "    ctx.textBaseline = 'middle';\n",
    "    ctx.font = '5px \"Noto Sans CJK SC\", \"Microsoft YaHei\", sans-serif';\n",
    "    const pid = Number(pidInput.value), k = Number(kInput.value);\n",
    "    let count = 0;\n",
    "    for (let row = 0; row < matrix.rows; row += 1) {\n",
    "      for (let col = 0; col < matrix.cols; col += 1) {\n",
    "        const kind = getHighlight(activeMatrix, row, col, pid, k);\n",
    "        if (kind === 'strong' || kind === 'strong-c') ctx.fillStyle = '#2563eb';\n",
    "        else if (kind === 'weak') ctx.fillStyle = '#d7eef4';\n",
    "        else ctx.fillStyle = '#ffffff';\n",
    "        ctx.fillRect(col * CELL, row * CELL, CELL, CELL);\n",
    "        ctx.strokeStyle = '#c9d2df';\n",
    "        ctx.lineWidth = 1 / state.scale;\n",
    "        ctx.strokeRect(col * CELL, row * CELL, CELL, CELL);\n",
    "        ctx.fillStyle = kind === 'strong' || kind === 'strong-c' ? '#ffffff' : '#172033';\n",
    "        ctx.fillText(`${matrix.label}[${row},${col}]`, col * CELL + CELL / 2, row * CELL + 11);\n",
    "        ctx.fillText(`@${address(activeMatrix,row,col)}`, col * CELL + CELL / 2, row * CELL + 21);\n",
    "        count += 1;\n",
    "      }\n",
    "    }\n",
    "    const item = mappings[pid];\n",
    "    const block = activeMatrix === 'A'\n",
    "      ? {row:item.pidM * cfg.BM, col:k * cfg.BK, rows:cfg.BM, cols:Math.min(cfg.BK, cfg.K - k * cfg.BK)}\n",
    "      : activeMatrix === 'B'\n",
    "        ? {row:k * cfg.BK, col:item.pidN * cfg.BN, rows:Math.min(cfg.BK, cfg.K - k * cfg.BK), cols:cfg.BN}\n",
    "        : {row:item.pidM * cfg.BM, col:item.pidN * cfg.BN, rows:cfg.BM, cols:cfg.BN};\n",
    "    ctx.strokeStyle = '#2563eb';\n",
    "    ctx.lineWidth = 3 / state.scale;\n",
    "    ctx.strokeRect(block.col * CELL + 1, block.row * CELL + 1, block.cols * CELL - 2, block.rows * CELL - 2);\n",
    "    ctx.restore();\n",
    "    const rowStart = activeMatrix === 'A' ? item.pidM * cfg.BM : activeMatrix === 'B' ? k * cfg.BK : item.pidM * cfg.BM;\n",
    "    const colStart = activeMatrix === 'A' ? k * cfg.BK : activeMatrix === 'B' ? item.pidN * cfg.BN : item.pidN * cfg.BN;\n",
    "    const logicalRows = activeMatrix === 'A' ? cfg.BM : activeMatrix === 'B' ? cfg.BK : cfg.BM;\n",
    "    const logicalCols = activeMatrix === 'A' ? cfg.BK : cfg.BN;\n",
    "    const blockRows = activeMatrix === 'B' ? Math.min(cfg.BK, cfg.K - rowStart) : logicalRows;\n",
    "    const blockCols = activeMatrix === 'A' ? Math.min(cfg.BK, cfg.K - colStart) : logicalCols;\n",
    "    const firstAddress = address(activeMatrix, rowStart, colStart);\n",
    "    const lastAddress = address(activeMatrix, rowStart + blockRows - 1, colStart + blockCols - 1);\n",
    "    matrixStatus.textContent = `${matrix.label}：${matrix.rows * matrix.cols} 个元素，已绘制 ${count} 个标签；PID ${pid} / K${k}，逻辑块 ${logicalRows}×${logicalCols}，有效块 ${blockRows}×${blockCols}，有效地址 @${firstAddress}–@${lastAddress}`;\n",
    "    matrixStride.textContent = matrix.stride;\n",
    "    zoomValue.textContent = `${state.scale.toFixed(2)}×`;\n",
    "  }\n",
    "  function renderMatrix() {\n",
    "    zoomInput.value = String(matrices[activeMatrix].states.scale);\n",
    "    drawMatrix();\n",
    "  }\n",
    "  function selectMatrix(name) {\n",
    "    activeMatrix = name;\n",
    "    tabButtons.forEach(button => button.setAttribute('aria-selected', button.dataset.tab === name ? 'true' : 'false'));\n",
    "    renderMatrix();\n",
    "  }\n",
    "  function step(delta) {\n",
    "    let flat = Number(pidInput.value) * 3 + Number(kInput.value);\n",
    "    flat = (flat + delta + totalPids * 3) % (totalPids * 3);\n",
    "    pidInput.value = String(Math.floor(flat / 3));\n",
    "    kInput.value = String(flat % 3);\n",
    "    render();\n",
    "  }\n",
    "  function render() {\n",
    "    const pid = Number(pidInput.value), k = Number(kInput.value), item = mappings[pid];\n",
    "    pidValue.textContent = String(pid);\n",
    "    kValue.textContent = String(k);\n",
    "    status.textContent = `PID ${pid} → C(${item.pidM},${item.pidN})；当前 K${k}`;\n",
    "    const kEnd = Math.min(cfg.K, (k + 1) * cfg.BK);\n",
    "    ranges.textContent = `A[${item.pidM * cfg.BM}:${(item.pidM + 1) * cfg.BM},${k * cfg.BK}:${kEnd}] × B[${k * cfg.BK}:${kEnd},${item.pidN * cfg.BN}:${(item.pidN + 1) * cfg.BN}] → C[${item.pidM * cfg.BM}:${(item.pidM + 1) * cfg.BM},${item.pidN * cfg.BN}:${(item.pidN + 1) * cfg.BN}]`;\n",
    "    renderMapping(item);\n",
    "    renderIndexDetails(item, k);\n",
    "    drawMatrix();\n",
    "  }\n",
    "  function resize() {\n",
    "    const state = matrices[activeMatrix].states;\n",
    "    if (!state.initialized) fitView(activeMatrix); else drawMatrix();\n",
    "  }\n",
    "  pidInput.addEventListener('input', render);\n",
    "  kInput.addEventListener('input', render);\n",
    "  previous.addEventListener('click', () => step(-1));\n",
    "  next.addEventListener('click', () => step(1));\n",
    "  play.addEventListener('click', () => {\n",
    "    if (timer) { clearInterval(timer); timer = null; play.textContent = '播放'; return; }\n",
    "    play.textContent = '暂停';\n",
    "    timer = setInterval(() => step(1), 900);\n",
    "  });\n",
    "  tabButtons.forEach(button => button.addEventListener('click', () => selectMatrix(button.dataset.tab)));\n",
    "  root.querySelector('[data-action=\"fit\"]').addEventListener('click', () => fitView(activeMatrix));\n",
    "  root.querySelector('[data-action=\"locate\"]').addEventListener('click', () => locateView(activeMatrix));\n",
    "  root.querySelector('[data-role=\"zoom\"]').addEventListener('input', () => {\n",
    "    matrices[activeMatrix].states.scale = Number(zoomInput.value);\n",
    "    renderMatrix();\n",
    "  });\n",
    "  viewport.addEventListener('wheel', event => {\n",
    "    event.preventDefault();\n",
    "    const state = matrices[activeMatrix].states;\n",
    "    state.scale = Math.max(0.1, Math.min(3, state.scale * (event.deltaY < 0 ? 1.1 : 0.9)));\n",
    "    renderMatrix();\n",
    "  }, {passive:false});\n",
    "  canvas.addEventListener('pointerdown', event => {\n",
    "    dragging = true;\n",
    "    dragStart = {x:event.clientX, y:event.clientY, stateX:matrices[activeMatrix].states.x, stateY:matrices[activeMatrix].states.y};\n",
    "    canvas.classList.add('is-dragging');\n",
    "    canvas.setPointerCapture(event.pointerId);\n",
    "  });\n",
    "  canvas.addEventListener('pointermove', event => {\n",
    "    if (!dragging) return;\n",
    "    const state = matrices[activeMatrix].states;\n",
    "    state.x = dragStart.stateX + event.clientX - dragStart.x;\n",
    "    state.y = dragStart.stateY + event.clientY - dragStart.y;\n",
    "    renderMatrix();\n",
    "  });\n",
    "  canvas.addEventListener('pointerup', event => {\n",
    "    dragging = false;\n",
    "    canvas.classList.remove('is-dragging');\n",
    "    if (canvas.hasPointerCapture(event.pointerId)) canvas.releasePointerCapture(event.pointerId);\n",
    "  });\n",
    "  canvas.addEventListener('pointercancel', () => {\n",
    "    dragging = false;\n",
    "    canvas.classList.remove('is-dragging');\n",
    "  });\n",
    "  window.addEventListener('resize', resize);\n",
    "  fitView('A');\n",
    "  fitView('B');\n",
    "  fitView('C');\n",
    "  render();\n",
    "})();\n",
    "</script>\n",
    "\n",
    "'''\n",
    "display(HTML(matmul_full_process_html))\n"
   ],
   "id": "mfp-full-elements-html",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ],
      "text/html": [
       "\n",
       "<style>\n",
       "#mfp-matrices [data-role=\"matrix-viewport\"] {\n",
       "  position: relative;\n",
       "  width: 100%;\n",
       "  height: 520px;\n",
       "  overflow: hidden;\n",
       "  border: 1px solid var(--border);\n",
       "  background: #ffffff;\n",
       "}\n",
       "#mfp-matrices canvas {\n",
       "  display: block;\n",
       "  width: 100%;\n",
       "  height: 100%;\n",
       "  cursor: grab;\n",
       "  touch-action: none;\n",
       "}\n",
       "#mfp-matrices canvas.is-dragging { cursor: grabbing; }\n",
       "#matmul-full-process .mfp-params {\n",
       "  display: grid;\n",
       "  grid-template-columns: repeat(auto-fit, minmax(118px, 1fr));\n",
       "  gap: 6px;\n",
       "  margin: 8px 0 10px;\n",
       "}\n",
       "#matmul-full-process .mfp-param {\n",
       "  display: flex;\n",
       "  flex-direction: column;\n",
       "  align-items: center;\n",
       "  justify-content: center;\n",
       "  padding: 6px 8px;\n",
       "  border: 1px solid var(--border);\n",
       "  background: #ffffff;\n",
       "  text-align: center;\n",
       "  min-width: 0;\n",
       "  overflow-wrap: anywhere;\n",
       "}\n",
       "#matmul-full-process .mfp-param-label {\n",
       "  color: var(--muted-foreground);\n",
       "}\n",
       "#matmul-full-process .mfp-param-value {\n",
       "  font-weight: 500;\n",
       "}\n",
       "#matmul-full-process .mfp-index-layout {\n",
       "  display: grid;\n",
       "  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);\n",
       "  gap: 12px;\n",
       "}\n",
       "#matmul-full-process .mfp-index-section {\n",
       "  min-width: 0;\n",
       "  padding-top: 8px;\n",
       "  border-top: 1px solid var(--border);\n",
       "}\n",
       "#matmul-full-process .mfp-index-section .text-small { overflow-wrap: anywhere; }\n",
       "#matmul-full-process .mfp-scroll {\n",
       "  max-width: 100%;\n",
       "  overflow-x: auto;\n",
       "  padding-bottom: 4px;\n",
       "}\n",
       "#matmul-full-process .mfp-array {\n",
       "  display: flex;\n",
       "  gap: 3px;\n",
       "  width: max-content;\n",
       "  margin: 4px 0 8px;\n",
       "}\n",
       "#matmul-full-process .mfp-value {\n",
       "  display: inline-flex;\n",
       "  align-items: center;\n",
       "  justify-content: center;\n",
       "  min-width: 27px;\n",
       "  min-height: 25px;\n",
       "  padding: 2px 4px;\n",
       "  box-sizing: border-box;\n",
       "  border: 1px solid var(--border);\n",
       "  background: #ffffff;\n",
       "  color: var(--foreground);\n",
       "  font-size: 11px;\n",
       "}\n",
       "#matmul-full-process .mfp-value.is-true { background: #dcfce7; color: #14532d; }\n",
       "#matmul-full-process .mfp-value.is-false { background: #fee2e2; color: #7f1d1d; }\n",
       "#matmul-full-process .mfp-address-table {\n",
       "  border-collapse: collapse;\n",
       "  width: max-content;\n",
       "  margin-top: 4px;\n",
       "  font-variant-numeric: tabular-nums;\n",
       "}\n",
       "#matmul-full-process .mfp-address-table th,\n",
       "#matmul-full-process .mfp-address-table td {\n",
       "  min-width: 31px;\n",
       "  height: 25px;\n",
       "  padding: 2px 4px;\n",
       "  border: 1px solid var(--border);\n",
       "  background: #ffffff;\n",
       "  color: var(--foreground);\n",
       "  text-align: center;\n",
       "  font-size: 11px;\n",
       "  font-weight: 400;\n",
       "  white-space: nowrap;\n",
       "}\n",
       "#matmul-full-process .mfp-address-table th {\n",
       "  background: #eef2f7;\n",
       "  color: var(--muted-foreground);\n",
       "}\n",
       "#matmul-full-process .mfp-address-table td.is-true { background: #dcfce7; color: #14532d; }\n",
       "#matmul-full-process .mfp-address-table td.is-false { background: #fee2e2; color: #7f1d1d; }\n",
       "#matmul-full-process .mfp-address-table td.is-masked {\n",
       "  background: repeating-linear-gradient(135deg, #fee2e2 0, #fee2e2 5px, #fff7f7 5px, #fff7f7 10px);\n",
       "  color: #7f1d1d;\n",
       "}\n",
       "@media (max-width: 520px) {\n",
       "  #mfp-matrices [data-role=\"matrix-viewport\"] { height: 420px; }\n",
       "  #matmul-full-process .mfp-index-layout { grid-template-columns: 1fr; }\n",
       "  #matmul-full-process .mfp-params { grid-template-columns: 1fr; }\n",
       "  #matmul-full-process [data-role=\"ranges\"] { width: 100%; text-align: left !important; }\n",
       "}\n",
       "</style>\n",
       "\n",
       "<div id=\"matmul-full-process\" style=\"width:100%;box-sizing:border-box;padding:16px;border:1px solid #cbd5e1;border-radius:8px;background:#f8fafc;color:#172033;color-scheme:light;font-family:'Noto Sans CJK SC','Microsoft YaHei',ui-sans-serif,system-ui,sans-serif;--foreground:#172033;--muted-foreground:#5b6475;--border:#c9d2df;--primary:#2563eb;--primary-foreground:#ffffff;--viz-series-1:#2563eb;--viz-series-2:#0891b2;--viz-series-3:#d97706;--muted:#e2e8f0;\">\n",
       "  <div class=\"viz-controls\" style=\"align-items:flex-end;margin-bottom:10px;\">\n",
       "    <label class=\"form-label\" style=\"min-width:210px;flex:1;\">\n",
       "      <span>PID <output data-role=\"pid-value\">0</output> / 19</span>\n",
       "      <input class=\"form-range\" data-role=\"pid\" type=\"range\" min=\"0\" max=\"19\" value=\"0\" step=\"1\">\n",
       "    </label>\n",
       "    <label class=\"form-label\" style=\"min-width:180px;flex:1;\">\n",
       "      <span>K 阶段 <output data-role=\"k-value\">0</output> / 2</span>\n",
       "      <input class=\"form-range\" data-role=\"k\" type=\"range\" min=\"0\" max=\"2\" value=\"0\" step=\"1\">\n",
       "    </label>\n",
       "    <button class=\"btn\" type=\"button\" data-action=\"previous\">上一步</button>\n",
       "    <button class=\"btn btn-primary\" type=\"button\" data-action=\"play\">播放</button>\n",
       "    <button class=\"btn\" type=\"button\" data-action=\"next\">下一步</button>\n",
       "  </div>\n",
       "  <div class=\"viz-row text-small\" aria-live=\"polite\" style=\"justify-content:space-between;gap:8px;margin-bottom:8px;\">\n",
       "    <span data-role=\"status\"></span>\n",
       "    <span data-role=\"ranges\" style=\"overflow-wrap:anywhere;text-align:right;\"></span>\n",
       "  </div>\n",
       "  <div class=\"mfp-params\" aria-label=\"当前矩阵和块参数\">\n",
       "    <span class=\"mfp-param\"><span class=\"mfp-param-label\">M</span><span class=\"mfp-param-value\">80</span></span>\n",
       "    <span class=\"mfp-param\"><span class=\"mfp-param-label\">N</span><span class=\"mfp-param-value\">80</span></span>\n",
       "    <span class=\"mfp-param\"><span class=\"mfp-param-label\">K</span><span class=\"mfp-param-value\">5</span></span>\n",
       "    <span class=\"mfp-param\"><span class=\"mfp-param-label\">BLOCK_SIZE_M</span><span class=\"mfp-param-value\">16</span></span>\n",
       "    <span class=\"mfp-param\"><span class=\"mfp-param-label\">BLOCK_SIZE_N</span><span class=\"mfp-param-value\">20</span></span>\n",
       "    <span class=\"mfp-param\"><span class=\"mfp-param-label\">BLOCK_SIZE_K</span><span class=\"mfp-param-value\">2</span></span>\n",
       "    <span class=\"mfp-param\"><span class=\"mfp-param-label\">GROUP_SIZE</span><span class=\"mfp-param-value\">2</span></span>\n",
       "  </div>\n",
       "\n",
       "  <div style=\"font-weight:500;margin:8px 0 3px;\">1. 一维 PID → 二维 PID 网格 → A、B、C 块网格</div>\n",
       "  <svg data-role=\"mapping\" viewBox=\"0 0 920 550\" role=\"img\" aria-labelledby=\"mfp-map-title mfp-map-desc\" style=\"display:block;width:100%;height:auto;\">\n",
       "    <title id=\"mfp-map-title\">一维 PID 到二维 PID 网格和 A、B、C 块的 group-major 映射</title>\n",
       "    <desc id=\"mfp-map-desc\">20 个一维 PID 先映射到五行四列的二维逻辑 PID 网格，再映射到 A、B、C 的块网格。</desc>\n",
       "  </svg>\n",
       "\n",
       "  <div style=\"font-weight:500;margin:12px 0 4px;\">2. 当前 PID/K 的 offsets、地址矩阵与 mask</div>\n",
       "  <div class=\"text-small\" data-role=\"index-summary\" aria-live=\"polite\" style=\"margin-bottom:8px;color:var(--muted-foreground);\"></div>\n",
       "  <div class=\"mfp-index-layout\">\n",
       "    <section class=\"mfp-index-section\">\n",
       "      <div style=\"font-weight:500;\">offset 向量</div>\n",
       "      <div data-role=\"offset-vectors\"></div>\n",
       "    </section>\n",
       "    <section class=\"mfp-index-section\">\n",
       "      <div style=\"font-weight:500;\">K 加载 mask</div>\n",
       "      <div data-role=\"load-mask\"></div>\n",
       "    </section>\n",
       "    <section class=\"mfp-index-section\">\n",
       "      <div style=\"font-weight:500;\">a_offsets：16×2</div>\n",
       "      <div class=\"text-small\" data-role=\"a-formula\" style=\"color:var(--muted-foreground);\"></div>\n",
       "      <div class=\"mfp-scroll\" data-role=\"a-offsets\"></div>\n",
       "    </section>\n",
       "    <section class=\"mfp-index-section\">\n",
       "      <div style=\"font-weight:500;\">b_offsets：2×20</div>\n",
       "      <div class=\"text-small\" data-role=\"b-formula\" style=\"color:var(--muted-foreground);\"></div>\n",
       "      <div class=\"mfp-scroll\" data-role=\"b-offsets\"></div>\n",
       "    </section>\n",
       "    <section class=\"mfp-index-section\" style=\"grid-column:1/-1;\">\n",
       "      <div style=\"font-weight:500;\">c_offsets：16×20</div>\n",
       "      <div class=\"text-small\" data-role=\"c-formula\" style=\"color:var(--muted-foreground);\"></div>\n",
       "      <div class=\"mfp-scroll\" data-role=\"c-offsets\"></div>\n",
       "    </section>\n",
       "    <section class=\"mfp-index-section\" style=\"grid-column:1/-1;\">\n",
       "      <div style=\"font-weight:500;\">c_mask：16×20</div>\n",
       "      <div class=\"text-small\" data-role=\"c-mask-formula\" style=\"color:var(--muted-foreground);\"></div>\n",
       "      <div class=\"mfp-scroll\" data-role=\"c-mask\"></div>\n",
       "    </section>\n",
       "  </div>\n",
       "\n",
       "  <div id=\"mfp-matrices\">\n",
       "    <div class=\"viz-row\" style=\"align-items:center;justify-content:space-between;margin:10px 0 5px;\">\n",
       "      <div class=\"viz-row\" role=\"tablist\" aria-label=\"矩阵选择\">\n",
       "        <button class=\"btn\" type=\"button\" role=\"tab\" aria-selected=\"true\" data-tab=\"A\">A (80×5)</button>\n",
       "        <button class=\"btn\" type=\"button\" role=\"tab\" aria-selected=\"false\" data-tab=\"B\">B (5×80)</button>\n",
       "        <button class=\"btn\" type=\"button\" role=\"tab\" aria-selected=\"false\" data-tab=\"C\">C (80×80)</button>\n",
       "      </div>\n",
       "      <div class=\"viz-row\">\n",
       "        <button class=\"btn\" type=\"button\" data-action=\"fit\">完整矩阵</button>\n",
       "        <button class=\"btn\" type=\"button\" data-action=\"locate\">定位当前块</button>\n",
       "      </div>\n",
       "    </div>\n",
       "    <div class=\"viz-row text-small\" style=\"justify-content:space-between;align-items:flex-start;gap:8px;margin-bottom:5px;\">\n",
       "      <span data-role=\"matrix-status\"></span>\n",
       "      <span data-role=\"matrix-stride\" style=\"overflow-wrap:anywhere;text-align:right;\"></span>\n",
       "    </div>\n",
       "    <div class=\"viz-row text-small\" style=\"justify-content:flex-end;margin-bottom:5px;\">\n",
       "      <label style=\"display:flex;align-items:center;gap:6px;\">\n",
       "        <span>缩放</span>\n",
       "        <input class=\"form-range\" data-role=\"zoom\" type=\"range\" min=\"0.1\" max=\"3\" value=\"1\" step=\"0.05\" style=\"width:150px;\">\n",
       "        <output data-role=\"zoom-value\">1.00×</output>\n",
       "      </label>\n",
       "    </div>\n",
       "    <div data-role=\"matrix-viewport\">\n",
       "      <canvas data-role=\"matrix-canvas\" aria-label=\"矩阵坐标和线性地址图\"></canvas>\n",
       "    </div>\n",
       "    <div class=\"viz-row text-small\" style=\"margin-top:6px;\">\n",
       "      <span><span aria-hidden=\"true\" style=\"display:inline-block;width:12px;height:12px;background:var(--primary);margin-right:5px;\"></span>当前 K 阶段</span>\n",
       "      <span><span aria-hidden=\"true\" style=\"display:inline-block;width:12px;height:12px;background:color-mix(in srgb,var(--viz-series-2) 25%,transparent);margin-right:5px;\"></span>同一 PID 的其他 K 阶段</span>\n",
       "      <span><span aria-hidden=\"true\" style=\"display:inline-block;width:12px;height:12px;background:color-mix(in srgb,var(--viz-series-3) 25%,transparent);margin-right:5px;\"></span>当前 PID 的 C 块</span>\n",
       "    </div>\n",
       "  </div>\n",
       "</div>\n",
       "\n",
       "<script>\n",
       "(() => {\n",
       "  const root = document.getElementById('matmul-full-process');\n",
       "  const pidInput = root.querySelector('[data-role=\"pid\"]');\n",
       "  const kInput = root.querySelector('[data-role=\"k\"]');\n",
       "  const pidValue = root.querySelector('[data-role=\"pid-value\"]');\n",
       "  const kValue = root.querySelector('[data-role=\"k-value\"]');\n",
       "  const status = root.querySelector('[data-role=\"status\"]');\n",
       "  const ranges = root.querySelector('[data-role=\"ranges\"]');\n",
       "  const mappingSvg = root.querySelector('[data-role=\"mapping\"]');\n",
       "  const indexSummary = root.querySelector('[data-role=\"index-summary\"]');\n",
       "  const offsetVectors = root.querySelector('[data-role=\"offset-vectors\"]');\n",
       "  const loadMask = root.querySelector('[data-role=\"load-mask\"]');\n",
       "  const aFormula = root.querySelector('[data-role=\"a-formula\"]');\n",
       "  const bFormula = root.querySelector('[data-role=\"b-formula\"]');\n",
       "  const cFormula = root.querySelector('[data-role=\"c-formula\"]');\n",
       "  const cMaskFormula = root.querySelector('[data-role=\"c-mask-formula\"]');\n",
       "  const aOffsets = root.querySelector('[data-role=\"a-offsets\"]');\n",
       "  const bOffsets = root.querySelector('[data-role=\"b-offsets\"]');\n",
       "  const cOffsets = root.querySelector('[data-role=\"c-offsets\"]');\n",
       "  const cMask = root.querySelector('[data-role=\"c-mask\"]');\n",
       "  const matrixStatus = root.querySelector('[data-role=\"matrix-status\"]');\n",
       "  const matrixStride = root.querySelector('[data-role=\"matrix-stride\"]');\n",
       "  const canvas = root.querySelector('[data-role=\"matrix-canvas\"]');\n",
       "  const viewport = root.querySelector('[data-role=\"matrix-viewport\"]');\n",
       "  const zoomInput = root.querySelector('[data-role=\"zoom\"]');\n",
       "  const zoomValue = root.querySelector('[data-role=\"zoom-value\"]');\n",
       "  const previous = root.querySelector('[data-action=\"previous\"]');\n",
       "  const next = root.querySelector('[data-action=\"next\"]');\n",
       "  const play = root.querySelector('[data-action=\"play\"]');\n",
       "  const tabButtons = Array.from(root.querySelectorAll('[data-tab]'));\n",
       "  const ctx = canvas.getContext('2d');\n",
       "  const ns = 'http://www.w3.org/2000/svg';\n",
       "  const cfg = {M:80, N:80, K:5, BM:16, BN:20, BK:2, GROUP_SIZE:2};\n",
       "  const numM = 5, numN = 4, totalPids = 20, CELL = 30;\n",
       "  const matrices = {\n",
       "    A: {rows:80, cols:5, label:'A', stride:'stride_a_M=5 · stride_a_K=1', states:{scale:1, x:0, y:0, initialized:false}},\n",
       "    B: {rows:5, cols:80, label:'B', stride:'stride_b_K=80 · stride_b_N=1', states:{scale:1, x:0, y:0, initialized:false}},\n",
       "    C: {rows:80, cols:80, label:'C', stride:'stride_c_M=80 · stride_c_N=1', states:{scale:1, x:0, y:0, initialized:false}}\n",
       "  };\n",
       "  let activeMatrix = 'A', timer = null, dragging = false, dragStart = null;\n",
       "\n",
       "  function mapPid(pid) {\n",
       "    const groupWidth = cfg.GROUP_SIZE * numN;\n",
       "    const groupId = Math.floor(pid / groupWidth);\n",
       "    const firstM = groupId * cfg.GROUP_SIZE;\n",
       "    const groupSizeAdj = Math.min(numM - firstM, cfg.GROUP_SIZE);\n",
       "    const localPid = pid % groupWidth;\n",
       "    const pidM = firstM + (localPid % groupSizeAdj);\n",
       "    const pidN = Math.floor(localPid / groupSizeAdj);\n",
       "    return {pid, groupId, groupSizeAdj, localPid, pidM, pidN};\n",
       "  }\n",
       "  const mappings = Array.from({length:totalPids}, (_, pid) => mapPid(pid));\n",
       "\n",
       "  function el(name, attrs = {}, content = '') {\n",
       "    const item = document.createElementNS(ns, name);\n",
       "    Object.entries(attrs).forEach(([key, value]) => item.setAttribute(key, String(value)));\n",
       "    if (content !== '') item.textContent = content;\n",
       "    return item;\n",
       "  }\n",
       "  function label(svg, x, y, content, attrs = {}) {\n",
       "    svg.appendChild(el('text', {x, y, fill:'var(--foreground)', 'font-size':13, 'text-anchor':'middle', 'dominant-baseline':'middle', ...attrs}, content));\n",
       "  }\n",
       "  function rect(svg, x, y, width, height, attrs = {}) {\n",
       "    svg.appendChild(el('rect', {x, y, width, height, rx:2, fill:'transparent', stroke:'var(--border)', ...attrs}));\n",
       "  }\n",
       "  function addArrowMarker(svg, id, color) {\n",
       "    const defs = el('defs');\n",
       "    const marker = el('marker', {id, viewBox:'0 0 10 10', refX:9, refY:5, markerWidth:6, markerHeight:6, orient:'auto'});\n",
       "    marker.appendChild(el('path', {d:'M 0 0 L 10 5 L 0 10 z', fill:color}));\n",
       "    defs.appendChild(marker);\n",
       "    svg.appendChild(defs);\n",
       "  }\n",
       "  function arrow(svg, path, color = 'var(--viz-series-1)', marker = 'mfp-arrow-main') {\n",
       "    svg.appendChild(el('path', {d:path, fill:'none', stroke:'#ffffff', 'stroke-width':6, 'stroke-linejoin':'round'}));\n",
       "    svg.appendChild(el('path', {d:path, fill:'none', stroke:color, 'stroke-width':2, 'stroke-linejoin':'round', 'marker-end':`url(#${marker})`}));\n",
       "  }\n",
       "  function blockGrid(svg, x, y, rows, cols, selectedRow, selectedCol, cellW, cellH, prefix) {\n",
       "    for (let row = 0; row < rows; row += 1) {\n",
       "      for (let col = 0; col < cols; col += 1) {\n",
       "        const selected = row === selectedRow && col === selectedCol;\n",
       "        rect(svg, x + col * cellW, y + row * cellH, cellW - 4, cellH - 4, {\n",
       "          fill:selected ? 'var(--primary)' : (row === rows - 1 ? 'color-mix(in srgb,var(--viz-series-3) 26%,transparent)' : 'color-mix(in srgb,var(--viz-series-2) 16%,transparent)'),\n",
       "          stroke:selected ? 'var(--primary)' : 'var(--border)'\n",
       "        });\n",
       "        label(svg, x + col * cellW + (cellW - 4) / 2, y + row * cellH + (cellH - 4) / 2, `${prefix}[${row},${col}]`, {\n",
       "          fill:selected ? 'var(--primary-foreground)' : 'var(--foreground)', 'font-size':11, 'font-weight':selected ? 500 : 400\n",
       "        });\n",
       "      }\n",
       "    }\n",
       "  }\n",
       "  function twoLineLabel(svg, x, y, first, second, attrs = {}) {\n",
       "    const text = el('text', {x, y:y - 5, fill:'var(--foreground)', 'font-size':11, 'text-anchor':'middle', ...attrs});\n",
       "    text.appendChild(el('tspan', {x, dy:0}, first));\n",
       "    text.appendChild(el('tspan', {x, dy:12}, second));\n",
       "    svg.appendChild(text);\n",
       "  }\n",
       "  function renderMapping(selected) {\n",
       "    mappingSvg.replaceChildren();\n",
       "    addArrowMarker(mappingSvg, 'mfp-arrow-main', 'var(--viz-series-1)');\n",
       "    addArrowMarker(mappingSvg, 'mfp-arrow-a', 'var(--viz-series-1)');\n",
       "    addArrowMarker(mappingSvg, 'mfp-arrow-b', 'var(--viz-series-2)');\n",
       "    addArrowMarker(mappingSvg, 'mfp-arrow-c', 'var(--viz-series-3)');\n",
       "    mappingSvg.appendChild(el('rect', {x:0, y:0, width:920, height:550, fill:'#ffffff', stroke:'var(--border)', 'stroke-width':1}));\n",
       "\n",
       "    const stripX = 20, stripY = 40, cellW = 42;\n",
       "    const pidGridX = 360, pidGridY = 155, pidCellW = 50, pidCellH = 32;\n",
       "    const aX = 35, bX = 335, cX = 655, gridY = 390;\n",
       "    const sourceX = stripX + selected.pid * cellW + 18;\n",
       "    const pidTargetX = pidGridX + selected.pidN * pidCellW + 23;\n",
       "    const pidTargetY = pidGridY + selected.pidM * pidCellH + 14;\n",
       "    const aTargetX = aX + Number(kInput.value) * 60 + 28;\n",
       "    const bTargetX = bX + selected.pidN * 60 + 28;\n",
       "    const cTargetX = cX + selected.pidN * 60 + 28;\n",
       "    const aTargetY = gridY + selected.pidM * 24 + 10;\n",
       "    const bTargetY = gridY + Number(kInput.value) * 24 + 10;\n",
       "    const cTargetY = gridY + selected.pidM * 24 + 10;\n",
       "\n",
       "    // 长连接线先绘制，随后由网格覆盖穿越部分；目标单元内再绘制短箭头。\n",
       "    arrow(mappingSvg, `M ${sourceX} 70 V 112 H 600 V ${pidTargetY} H ${pidTargetX}`);\n",
       "    arrow(mappingSvg, `M ${pidTargetX - 12} ${pidTargetY} V 330 H 24 V ${aTargetY} H ${aTargetX}`, 'var(--viz-series-1)', 'mfp-arrow-a');\n",
       "    arrow(mappingSvg, `M ${pidTargetX} ${pidTargetY} V 340 H 324 V ${bTargetY} H ${bTargetX}`, 'var(--viz-series-2)', 'mfp-arrow-b');\n",
       "    arrow(mappingSvg, `M ${pidTargetX + 12} ${pidTargetY} V 350 H 908 V ${cTargetY} H ${cTargetX}`, 'var(--viz-series-3)', 'mfp-arrow-c');\n",
       "\n",
       "    label(mappingSvg, 20, 20, '一维启动网格：20 个程序实例', {'text-anchor':'start', fill:'var(--muted-foreground)'});\n",
       "    mappings.forEach(item => {\n",
       "      const active = item.pid === selected.pid;\n",
       "      rect(mappingSvg, stripX + item.pid * cellW, stripY, cellW - 3, 30, {\n",
       "        fill:active ? 'var(--primary)' : (item.groupId === 2 ? 'color-mix(in srgb,var(--viz-series-3) 30%,transparent)' : 'color-mix(in srgb,var(--viz-series-2) 16%,transparent)'),\n",
       "        stroke:active ? 'var(--primary)' : 'var(--border)'\n",
       "      });\n",
       "      label(mappingSvg, stripX + item.pid * cellW + (cellW - 3) / 2, stripY + 15, String(item.pid), {\n",
       "        fill:active ? 'var(--primary-foreground)' : 'var(--foreground)', 'font-size':11, 'font-weight':active ? 500 : 400\n",
       "      });\n",
       "    });\n",
       "    [0,8,16].forEach((start, group) => {\n",
       "      const count = group < 2 ? 8 : 4;\n",
       "      const x1 = stripX + start * cellW, x2 = x1 + count * cellW - 3;\n",
       "      mappingSvg.appendChild(el('path', {d:`M ${x1} 75 V 81 H ${x2} V 75`, fill:'none', stroke:group === 2 ? 'var(--viz-series-3)' : 'var(--muted-foreground)'}));\n",
       "      label(mappingSvg, (x1 + x2) / 2, 94, `group ${group}`, {'font-size':11, fill:group === 2 ? 'var(--viz-series-3)' : 'var(--muted-foreground)'});\n",
       "    });\n",
       "    label(mappingSvg, pidGridX + 100, 130, '二维逻辑 PID 网格（PID_M × PID_N = 5×4）', {'font-weight':500});\n",
       "    for (let row = 0; row < numM; row += 1) {\n",
       "      for (let col = 0; col < numN; col += 1) {\n",
       "        const item = mappings.find(entry => entry.pidM === row && entry.pidN === col);\n",
       "        const active = item.pid === selected.pid;\n",
       "        rect(mappingSvg, pidGridX + col * pidCellW, pidGridY + row * pidCellH, pidCellW - 4, pidCellH - 4, {\n",
       "          fill:active ? 'var(--primary)' : (item.groupId === 2 ? 'color-mix(in srgb,var(--viz-series-3) 28%,transparent)' : 'color-mix(in srgb,var(--viz-series-2) 16%,transparent)'),\n",
       "          stroke:active ? 'var(--primary)' : 'var(--border)'\n",
       "        });\n",
       "        twoLineLabel(mappingSvg, pidGridX + col * pidCellW + 23, pidGridY + row * pidCellH + 14, `PID ${item.pid}`, `(${row},${col})`, {\n",
       "          fill:active ? 'var(--primary-foreground)' : 'var(--foreground)', 'font-weight':active ? 500 : 400\n",
       "        });\n",
       "      }\n",
       "    }\n",
       "    label(mappingSvg, 20, 140, `PID ${selected.pid} 的映射计算`, {'text-anchor':'start', 'font-weight':500});\n",
       "    label(mappingSvg, 20, 165, `group_id = ${selected.groupId}`, {'text-anchor':'start', 'font-size':12});\n",
       "    label(mappingSvg, 20, 187, `local_PID = ${selected.localPid}`, {'text-anchor':'start', 'font-size':12});\n",
       "    label(mappingSvg, 20, 209, `group_size_adj = ${selected.groupSizeAdj}`, {'text-anchor':'start', 'font-size':12});\n",
       "    label(mappingSvg, 20, 231, `PID_M = ${selected.pidM}`, {'text-anchor':'start', 'font-size':12});\n",
       "    label(mappingSvg, 20, 253, `PID_N = ${selected.pidN}`, {'text-anchor':'start', 'font-size':12});\n",
       "    label(mappingSvg, aX + 90, 356, 'A 块网格：5×3，每块 16×2', {'font-weight':500});\n",
       "    label(mappingSvg, bX + 120, 356, 'B 块网格：3×4，每块 2×20', {'font-weight':500});\n",
       "    label(mappingSvg, cX + 120, 356, 'C 块网格：5×4，每块 16×20', {'font-weight':500});\n",
       "    blockGrid(mappingSvg, aX, gridY, 5, 3, selected.pidM, Number(kInput.value), 60, 24, 'A');\n",
       "    blockGrid(mappingSvg, bX, gridY, 3, 4, Number(kInput.value), selected.pidN, 60, 24, 'B');\n",
       "    blockGrid(mappingSvg, cX, gridY, 5, 4, selected.pidM, selected.pidN, 60, 24, 'C');\n",
       "    arrow(mappingSvg, `M ${pidGridX + selected.pidN * pidCellW + 46} ${pidTargetY} H ${pidTargetX}`);\n",
       "    arrow(mappingSvg, `M ${aX + Number(kInput.value) * 60} ${aTargetY} H ${aTargetX}`, 'var(--viz-series-1)', 'mfp-arrow-a');\n",
       "    arrow(mappingSvg, `M ${bX + selected.pidN * 60} ${bTargetY} H ${bTargetX}`, 'var(--viz-series-2)', 'mfp-arrow-b');\n",
       "    arrow(mappingSvg, `M ${cX + selected.pidN * 60 + 56} ${cTargetY} H ${cTargetX}`, 'var(--viz-series-3)', 'mfp-arrow-c');\n",
       "    // 将选中单元标签置于箭头之上，避免箭头遮挡文字。\n",
       "    twoLineLabel(mappingSvg, pidTargetX, pidTargetY, `PID ${selected.pid}`, `(${selected.pidM},${selected.pidN})`, {fill:'var(--primary-foreground)', 'font-weight':500});\n",
       "    label(mappingSvg, aTargetX, aTargetY, `A[${selected.pidM},${Number(kInput.value)}]`, {fill:'var(--primary-foreground)', 'font-size':11, 'font-weight':500});\n",
       "    label(mappingSvg, bTargetX, bTargetY, `B[${Number(kInput.value)},${selected.pidN}]`, {fill:'var(--primary-foreground)', 'font-size':11, 'font-weight':500});\n",
       "    label(mappingSvg, cTargetX, cTargetY, `C[${selected.pidM},${selected.pidN}]`, {fill:'var(--primary-foreground)', 'font-size':11, 'font-weight':500});\n",
       "    label(mappingSvg, 20, 523, `当前块：A[${selected.pidM},${Number(kInput.value)}] · B[${Number(kInput.value)},${selected.pidN}] · C[${selected.pidM},${selected.pidN}]`, {'text-anchor':'start', 'font-size':12, 'font-weight':500});\n",
       "    const kEnd = Math.min(cfg.K, (Number(kInput.value) + 1) * cfg.BK);\n",
       "    label(mappingSvg, 20, 541, `有效范围：A[${selected.pidM * cfg.BM}:${(selected.pidM + 1) * cfg.BM},${Number(kInput.value) * cfg.BK}:${kEnd}] · B[${Number(kInput.value) * cfg.BK}:${kEnd},${selected.pidN * cfg.BN}:${(selected.pidN + 1) * cfg.BN}] · C[${selected.pidM * cfg.BM}:${(selected.pidM + 1) * cfg.BM},${selected.pidN * cfg.BN}:${(selected.pidN + 1) * cfg.BN}]`, {'text-anchor':'start', 'font-size':10, fill:'var(--muted-foreground)'});\n",
       "  }\n",
       "\n",
       "  function makeHtml(name, className = '', content = '') {\n",
       "    const item = document.createElement(name);\n",
       "    if (className) item.className = className;\n",
       "    if (content !== '') item.textContent = content;\n",
       "    return item;\n",
       "  }\n",
       "  function appendArray(container, name, values, booleanValues = false) {\n",
       "    const labelNode = makeHtml('div', 'text-small', `${name}（${values.length} 项）`);\n",
       "    labelNode.style.color = 'var(--muted-foreground)';\n",
       "    const scroll = makeHtml('div', 'mfp-scroll');\n",
       "    const row = makeHtml('div', 'mfp-array');\n",
       "    values.forEach(value => {\n",
       "      const cell = makeHtml('span', 'mfp-value', booleanValues ? (value ? 'True' : 'False') : String(value));\n",
       "      if (booleanValues) cell.classList.add(value ? 'is-true' : 'is-false');\n",
       "      row.appendChild(cell);\n",
       "    });\n",
       "    scroll.appendChild(row);\n",
       "    container.append(labelNode, scroll);\n",
       "  }\n",
       "  function renderTable(container, values, rowLabels, colLabels, booleanValues = false, validity = null) {\n",
       "    container.replaceChildren();\n",
       "    const table = makeHtml('table', 'mfp-address-table');\n",
       "    const head = makeHtml('thead');\n",
       "    const headRow = makeHtml('tr');\n",
       "    headRow.appendChild(makeHtml('th', '', ''));\n",
       "    colLabels.forEach(value => headRow.appendChild(makeHtml('th', '', value)));\n",
       "    head.appendChild(headRow);\n",
       "    table.appendChild(head);\n",
       "    const body = makeHtml('tbody');\n",
       "    values.forEach((row, rowIndex) => {\n",
       "      const tableRow = makeHtml('tr');\n",
       "      tableRow.appendChild(makeHtml('th', '', rowLabels[rowIndex]));\n",
       "      row.forEach((value, colIndex) => {\n",
       "        const valid = !validity || validity[rowIndex][colIndex];\n",
       "        const content = booleanValues ? (value ? 'T' : 'F') : `${value}${valid ? '' : ' ×'}`;\n",
       "        const cell = makeHtml('td', '', content);\n",
       "        if (booleanValues) cell.classList.add(value ? 'is-true' : 'is-false');\n",
       "        if (!valid) {\n",
       "          cell.classList.add('is-masked');\n",
       "          cell.setAttribute('aria-label', `${value}，已被 mask 屏蔽`);\n",
       "        }\n",
       "        tableRow.appendChild(cell);\n",
       "      });\n",
       "      body.appendChild(tableRow);\n",
       "    });\n",
       "    table.appendChild(body);\n",
       "    container.appendChild(table);\n",
       "  }\n",
       "  function sequence(start, length) {\n",
       "    return Array.from({length}, (_, index) => start + index);\n",
       "  }\n",
       "  function renderIndexDetails(selected, k) {\n",
       "    const offsetsM = sequence(selected.pidM * cfg.BM, cfg.BM);\n",
       "    const offsetsN = sequence(selected.pidN * cfg.BN, cfg.BN);\n",
       "    const offsetsK = sequence(0, cfg.BK);\n",
       "    const globalK = offsetsK.map(value => value + k * cfg.BK);\n",
       "    const loadMaskValues = offsetsK.map(value => value < cfg.K - k * cfg.BK);\n",
       "    const aOffsetValues = offsetsM.map(row => globalK.map(col => row * cfg.K + col));\n",
       "    const bOffsetValues = globalK.map(row => offsetsN.map(col => row * cfg.N + col));\n",
       "    const cOffsetValues = offsetsM.map(row => offsetsN.map(col => row * cfg.N + col));\n",
       "    const cMaskValues = offsetsM.map(row => offsetsN.map(col => row < cfg.M && col < cfg.N));\n",
       "    const aValidity = offsetsM.map(() => loadMaskValues.slice());\n",
       "    const bValidity = loadMaskValues.map(valid => offsetsN.map(() => valid));\n",
       "\n",
       "    const maskedCount = loadMaskValues.filter(value => !value).length;\n",
       "    indexSummary.textContent = `PID ${selected.pid} / K${k}：a_offsets 已推进 ${k * cfg.BK}，b_offsets 已推进 ${k * cfg.BK * 80}；加载 mask 屏蔽 ${maskedCount} 个 K 位置，c_mask 的 320 个位置全部为 True。`;\n",
       "    offsetVectors.replaceChildren();\n",
       "    appendArray(offsetVectors, 'offsets_M', offsetsM);\n",
       "    appendArray(offsetVectors, 'offsets_N', offsetsN);\n",
       "    appendArray(offsetVectors, 'offsets_K（内核基础向量）', offsetsK);\n",
       "    appendArray(offsetVectors, `当前全局 K = offsets_K + ${k}×BLOCK_SIZE_K`, globalK);\n",
       "\n",
       "    loadMask.replaceChildren();\n",
       "    const maskFormula = makeHtml('div', 'text-small', `mask = offsets_K < K - k×BLOCK_SIZE_K = [0,1] < ${cfg.K - k * cfg.BK}`);\n",
       "    maskFormula.style.color = 'var(--muted-foreground)';\n",
       "    loadMask.appendChild(maskFormula);\n",
       "    appendArray(loadMask, 'mask', loadMaskValues, true);\n",
       "    appendArray(loadMask, 'A 使用 mask[None, :]', loadMaskValues, true);\n",
       "    appendArray(loadMask, 'B 使用 mask[:, None]', loadMaskValues, true);\n",
       "\n",
       "    aFormula.textContent = `a_offsets = offsets_M[:,None]×stride_a_M + 当前全局K[None,:]×stride_a_K；stride_a_M=5，stride_a_K=1；× 表示该地址不会被加载`;\n",
       "    bFormula.textContent = `b_offsets = 当前全局K[:,None]×stride_b_K + offsets_N[None,:]×stride_b_N；stride_b_K=80，stride_b_N=1`;\n",
       "    cFormula.textContent = 'c_offsets = offsets_M[:,None]×stride_c_M + offsets_N[None,:]×stride_c_N；stride_c_M=80，stride_c_N=1';\n",
       "    cMaskFormula.textContent = 'c_mask = (offsets_M[:,None] < M) & (offsets_N[None,:] < N)；T=True，F=False';\n",
       "    renderTable(aOffsets, aOffsetValues, offsetsM.map(value => `M${value}`), globalK.map(value => `K${value}`), false, aValidity);\n",
       "    renderTable(bOffsets, bOffsetValues, globalK.map(value => `K${value}`), offsetsN.map(value => `N${value}`), false, bValidity);\n",
       "    renderTable(cOffsets, cOffsetValues, offsetsM.map(value => `M${value}`), offsetsN.map(value => `N${value}`));\n",
       "    renderTable(cMask, cMaskValues, offsetsM.map(value => `M${value}`), offsetsN.map(value => `N${value}`), true);\n",
       "  }\n",
       "\n",
       "  function getHighlight(name, row, col, pid, k) {\n",
       "    const item = mappings[pid];\n",
       "    if (name === 'C') return row >= item.pidM * cfg.BM && row < (item.pidM + 1) * cfg.BM && col >= item.pidN * cfg.BN && col < (item.pidN + 1) * cfg.BN ? 'strong-c' : '';\n",
       "    if (name === 'A') {\n",
       "      if (row < item.pidM * cfg.BM || row >= (item.pidM + 1) * cfg.BM) return '';\n",
       "      return Math.floor(col / cfg.BK) === k ? 'strong' : 'weak';\n",
       "    }\n",
       "    if (name === 'B') {\n",
       "      if (col < item.pidN * cfg.BN || col >= (item.pidN + 1) * cfg.BN) return '';\n",
       "      return Math.floor(row / cfg.BK) === k ? 'strong' : 'weak';\n",
       "    }\n",
       "    return '';\n",
       "  }\n",
       "  function address(name, row, col) {\n",
       "    if (name === 'A') return row * cfg.K + col;\n",
       "    return row * cfg.N + col;\n",
       "  }\n",
       "  function fitView(name) {\n",
       "    const state = matrices[name].states;\n",
       "    const bounds = viewport.getBoundingClientRect();\n",
       "    const worldW = matrices[name].cols * CELL, worldH = matrices[name].rows * CELL;\n",
       "    state.scale = Math.max(0.1, Math.min(3, Math.min((bounds.width - 20) / worldW, (bounds.height - 20) / worldH)));\n",
       "    state.x = (bounds.width - worldW * state.scale) / 2;\n",
       "    state.y = (bounds.height - worldH * state.scale) / 2;\n",
       "    state.initialized = true;\n",
       "    renderMatrix();\n",
       "  }\n",
       "  function locateView(name) {\n",
       "    const state = matrices[name].states, bounds = viewport.getBoundingClientRect(), item = mappings[Number(pidInput.value)], k = Number(kInput.value);\n",
       "    let rowStart, rowCount, colStart, colCount;\n",
       "    if (name === 'A') { rowStart = item.pidM * cfg.BM; rowCount = cfg.BM; colStart = k * cfg.BK; colCount = Math.min(cfg.BK, cfg.K - colStart); }\n",
       "    else if (name === 'B') { rowStart = k * cfg.BK; rowCount = Math.min(cfg.BK, cfg.K - rowStart); colStart = item.pidN * cfg.BN; colCount = cfg.BN; }\n",
       "    else { rowStart = item.pidM * cfg.BM; rowCount = cfg.BM; colStart = item.pidN * cfg.BN; colCount = cfg.BN; }\n",
       "    const targetScale = name === 'A' ? 1.45 : name === 'B' ? 0.9 : 0.72;\n",
       "    state.scale = targetScale;\n",
       "    state.x = bounds.width / 2 - (colStart + colCount / 2) * CELL * state.scale;\n",
       "    state.y = bounds.height / 2 - (rowStart + rowCount / 2) * CELL * state.scale;\n",
       "    state.initialized = true;\n",
       "    renderMatrix();\n",
       "  }\n",
       "  function drawMatrix() {\n",
       "    const matrix = matrices[activeMatrix], state = matrix.states, bounds = viewport.getBoundingClientRect(), dpr = window.devicePixelRatio || 1;\n",
       "    canvas.width = Math.max(1, Math.round(bounds.width * dpr));\n",
       "    canvas.height = Math.max(1, Math.round(bounds.height * dpr));\n",
       "    ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n",
       "    ctx.clearRect(0, 0, bounds.width, bounds.height);\n",
       "    ctx.save();\n",
       "    ctx.translate(state.x, state.y);\n",
       "    ctx.scale(state.scale, state.scale);\n",
       "    ctx.textAlign = 'center';\n",
       "    ctx.textBaseline = 'middle';\n",
       "    ctx.font = '5px \"Noto Sans CJK SC\", \"Microsoft YaHei\", sans-serif';\n",
       "    const pid = Number(pidInput.value), k = Number(kInput.value);\n",
       "    let count = 0;\n",
       "    for (let row = 0; row < matrix.rows; row += 1) {\n",
       "      for (let col = 0; col < matrix.cols; col += 1) {\n",
       "        const kind = getHighlight(activeMatrix, row, col, pid, k);\n",
       "        if (kind === 'strong' || kind === 'strong-c') ctx.fillStyle = '#2563eb';\n",
       "        else if (kind === 'weak') ctx.fillStyle = '#d7eef4';\n",
       "        else ctx.fillStyle = '#ffffff';\n",
       "        ctx.fillRect(col * CELL, row * CELL, CELL, CELL);\n",
       "        ctx.strokeStyle = '#c9d2df';\n",
       "        ctx.lineWidth = 1 / state.scale;\n",
       "        ctx.strokeRect(col * CELL, row * CELL, CELL, CELL);\n",
       "        ctx.fillStyle = kind === 'strong' || kind === 'strong-c' ? '#ffffff' : '#172033';\n",
       "        ctx.fillText(`${matrix.label}[${row},${col}]`, col * CELL + CELL / 2, row * CELL + 11);\n",
       "        ctx.fillText(`@${address(activeMatrix,row,col)}`, col * CELL + CELL / 2, row * CELL + 21);\n",
       "        count += 1;\n",
       "      }\n",
       "    }\n",
       "    const item = mappings[pid];\n",
       "    const block = activeMatrix === 'A'\n",
       "      ? {row:item.pidM * cfg.BM, col:k * cfg.BK, rows:cfg.BM, cols:Math.min(cfg.BK, cfg.K - k * cfg.BK)}\n",
       "      : activeMatrix === 'B'\n",
       "        ? {row:k * cfg.BK, col:item.pidN * cfg.BN, rows:Math.min(cfg.BK, cfg.K - k * cfg.BK), cols:cfg.BN}\n",
       "        : {row:item.pidM * cfg.BM, col:item.pidN * cfg.BN, rows:cfg.BM, cols:cfg.BN};\n",
       "    ctx.strokeStyle = '#2563eb';\n",
       "    ctx.lineWidth = 3 / state.scale;\n",
       "    ctx.strokeRect(block.col * CELL + 1, block.row * CELL + 1, block.cols * CELL - 2, block.rows * CELL - 2);\n",
       "    ctx.restore();\n",
       "    const rowStart = activeMatrix === 'A' ? item.pidM * cfg.BM : activeMatrix === 'B' ? k * cfg.BK : item.pidM * cfg.BM;\n",
       "    const colStart = activeMatrix === 'A' ? k * cfg.BK : activeMatrix === 'B' ? item.pidN * cfg.BN : item.pidN * cfg.BN;\n",
       "    const logicalRows = activeMatrix === 'A' ? cfg.BM : activeMatrix === 'B' ? cfg.BK : cfg.BM;\n",
       "    const logicalCols = activeMatrix === 'A' ? cfg.BK : cfg.BN;\n",
       "    const blockRows = activeMatrix === 'B' ? Math.min(cfg.BK, cfg.K - rowStart) : logicalRows;\n",
       "    const blockCols = activeMatrix === 'A' ? Math.min(cfg.BK, cfg.K - colStart) : logicalCols;\n",
       "    const firstAddress = address(activeMatrix, rowStart, colStart);\n",
       "    const lastAddress = address(activeMatrix, rowStart + blockRows - 1, colStart + blockCols - 1);\n",
       "    matrixStatus.textContent = `${matrix.label}：${matrix.rows * matrix.cols} 个元素，已绘制 ${count} 个标签；PID ${pid} / K${k}，逻辑块 ${logicalRows}×${logicalCols}，有效块 ${blockRows}×${blockCols}，有效地址 @${firstAddress}–@${lastAddress}`;\n",
       "    matrixStride.textContent = matrix.stride;\n",
       "    zoomValue.textContent = `${state.scale.toFixed(2)}×`;\n",
       "  }\n",
       "  function renderMatrix() {\n",
       "    zoomInput.value = String(matrices[activeMatrix].states.scale);\n",
       "    drawMatrix();\n",
       "  }\n",
       "  function selectMatrix(name) {\n",
       "    activeMatrix = name;\n",
       "    tabButtons.forEach(button => button.setAttribute('aria-selected', button.dataset.tab === name ? 'true' : 'false'));\n",
       "    renderMatrix();\n",
       "  }\n",
       "  function step(delta) {\n",
       "    let flat = Number(pidInput.value) * 3 + Number(kInput.value);\n",
       "    flat = (flat + delta + totalPids * 3) % (totalPids * 3);\n",
       "    pidInput.value = String(Math.floor(flat / 3));\n",
       "    kInput.value = String(flat % 3);\n",
       "    render();\n",
       "  }\n",
       "  function render() {\n",
       "    const pid = Number(pidInput.value), k = Number(kInput.value), item = mappings[pid];\n",
       "    pidValue.textContent = String(pid);\n",
       "    kValue.textContent = String(k);\n",
       "    status.textContent = `PID ${pid} → C(${item.pidM},${item.pidN})；当前 K${k}`;\n",
       "    const kEnd = Math.min(cfg.K, (k + 1) * cfg.BK);\n",
       "    ranges.textContent = `A[${item.pidM * cfg.BM}:${(item.pidM + 1) * cfg.BM},${k * cfg.BK}:${kEnd}] × B[${k * cfg.BK}:${kEnd},${item.pidN * cfg.BN}:${(item.pidN + 1) * cfg.BN}] → C[${item.pidM * cfg.BM}:${(item.pidM + 1) * cfg.BM},${item.pidN * cfg.BN}:${(item.pidN + 1) * cfg.BN}]`;\n",
       "    renderMapping(item);\n",
       "    renderIndexDetails(item, k);\n",
       "    drawMatrix();\n",
       "  }\n",
       "  function resize() {\n",
       "    const state = matrices[activeMatrix].states;\n",
       "    if (!state.initialized) fitView(activeMatrix); else drawMatrix();\n",
       "  }\n",
       "  pidInput.addEventListener('input', render);\n",
       "  kInput.addEventListener('input', render);\n",
       "  previous.addEventListener('click', () => step(-1));\n",
       "  next.addEventListener('click', () => step(1));\n",
       "  play.addEventListener('click', () => {\n",
       "    if (timer) { clearInterval(timer); timer = null; play.textContent = '播放'; return; }\n",
       "    play.textContent = '暂停';\n",
       "    timer = setInterval(() => step(1), 900);\n",
       "  });\n",
       "  tabButtons.forEach(button => button.addEventListener('click', () => selectMatrix(button.dataset.tab)));\n",
       "  root.querySelector('[data-action=\"fit\"]').addEventListener('click', () => fitView(activeMatrix));\n",
       "  root.querySelector('[data-action=\"locate\"]').addEventListener('click', () => locateView(activeMatrix));\n",
       "  root.querySelector('[data-role=\"zoom\"]').addEventListener('input', () => {\n",
       "    matrices[activeMatrix].states.scale = Number(zoomInput.value);\n",
       "    renderMatrix();\n",
       "  });\n",
       "  viewport.addEventListener('wheel', event => {\n",
       "    event.preventDefault();\n",
       "    const state = matrices[activeMatrix].states;\n",
       "    state.scale = Math.max(0.1, Math.min(3, state.scale * (event.deltaY < 0 ? 1.1 : 0.9)));\n",
       "    renderMatrix();\n",
       "  }, {passive:false});\n",
       "  canvas.addEventListener('pointerdown', event => {\n",
       "    dragging = true;\n",
       "    dragStart = {x:event.clientX, y:event.clientY, stateX:matrices[activeMatrix].states.x, stateY:matrices[activeMatrix].states.y};\n",
       "    canvas.classList.add('is-dragging');\n",
       "    canvas.setPointerCapture(event.pointerId);\n",
       "  });\n",
       "  canvas.addEventListener('pointermove', event => {\n",
       "    if (!dragging) return;\n",
       "    const state = matrices[activeMatrix].states;\n",
       "    state.x = dragStart.stateX + event.clientX - dragStart.x;\n",
       "    state.y = dragStart.stateY + event.clientY - dragStart.y;\n",
       "    renderMatrix();\n",
       "  });\n",
       "  canvas.addEventListener('pointerup', event => {\n",
       "    dragging = false;\n",
       "    canvas.classList.remove('is-dragging');\n",
       "    if (canvas.hasPointerCapture(event.pointerId)) canvas.releasePointerCapture(event.pointerId);\n",
       "  });\n",
       "  canvas.addEventListener('pointercancel', () => {\n",
       "    dragging = false;\n",
       "    canvas.classList.remove('is-dragging');\n",
       "  });\n",
       "  window.addEventListener('resize', resize);\n",
       "  fitView('A');\n",
       "  fitView('B');\n",
       "  fitView('C');\n",
       "  render();\n",
       "})();\n",
       "</script>\n",
       "\n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "execution_count": 3
  },
  {
   "metadata": {},
   "cell_type": "code",
   "outputs": [],
   "execution_count": null,
   "source": "",
   "id": "7aca8535e1f5ebdc"
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python (triton-tutorial)",
   "language": "python",
   "name": "triton-tutorial"
  },
  "language_info": {
   "name": "python",
   "version": "3.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
