Qwen3.8 27B 单16G显存显卡本地测试与部署教程(128K上下文)

Ruby 2026-08-23 10:55 1


1.基础知识


1.1 什么是本地部署


本地部署就是通过个人或者企业通过消费级设备或工作站设备,将云端开源大语言模型部署到本地私有化设备上的过程


1.2 本地部署所需的环境


硬件层面:一般而言需要一张消费级显卡,消费级显卡上满足显存≥16GB的显卡都可以适用本教程。具体见下表




























显卡厂商 起步型号 注解
Nvidia 4060 Ti 16G / 5060 Ti 16G 起步型号容易出现带宽瓶颈
AMD 7800 XT 需要使用 ROCm,入手难度较大
Intel A770 16G 需要使用 Vulkan,入手难度较大

软件层面:常用的推理后端包括vllm,sglang,llama.cpp等。最适合本地消费级显卡部署的后端是llama.cpp。



关于llama.cpp,市面上的两款比较火的chat desktop软件ollama/lm studio都是基于llama.cpp后端。这俩软件的好处是支持一键下载部署,缺点是对于一些特性支持不到位,且没有针对具体的显卡进行优化。所以本教程采用llama.cpp自部署与自编译



1.3 Qwen3.8-27B


一款具备27B(270e)参数的稠密模型,每次推理都需要所有参数进行一次完整计算,对显卡的核心算力与带宽都有很大的要求。

该模型基座基于qwen3.5,包含16组 3层线性注意力+1层全局注意力。

该模型参数量上FFN层占据大头,约为70-80%的参数量,所以对这些FFN层进行量化压缩才能在本地消费级显卡上部署


1.4 量化


量化就是对模型的参数存放在计算机中需要占据一定的bit位。参数多通过float point number(浮点数简称float/float point)的形式进行存储。

以fp32为例,一个fp32数需要占据32bit位,fp16则是16bit fp8需要8bit fp4需要4bit,int8 int4同理看数字即可。

llm的量化多以 Qx来平衡,Q4就是4bit左右的量化,有些量化带后缀,如Q4_km,Q4就是4bit量化,k则是采用聚类的组量化,m则是量化组的体量(s,m,x分别表示小,中,大)。

衡量一个模型的量化程度我们通常采用bpw(bit per word)来衡量,也就是每个词大概是多少bit。


1.5 KvCache


类似cpu 的cache,采用局部性原理,在llm里先前被计算过的kv,可能在后续被继续使用,采用kvcache可以加速llm的计算。 kvcache也需要进行计算机存储,常用的量化有fp16 q8 q4等,本教程涉及到一个新的量化方案——Turboquant


什么是turboquant?



Turboquant是谷歌26年初提出的一种kvcache量化策略,不直接对数值进行bit位量化,而是采用一种更加先进的量化策略,简而言之tb4的量化精度可以解决q8左右的量化精度。其中的tb4即采用4bit存储的意思



一般地,k和v会分别进行量化,常见的组合包括q8+q8/q8+q4/q4+q4,turboquant则可以采用tb4+tb4/tb4+tb3/tb3+tb3。通常认为k会比v更加重要,所以tb4+tb3是常用的量化方案


2.三款量化版本点评


我测试三款不同量化策略的模型。下面是它们的点评与下载链接(包含抱脸与模搭)


2.1 Ridge 3.7bpw


等效量化3.7bpw,量化策略非常激进的一版模型。这版量化主要针对FFN层进行量化,它的首尾部的FFN层采用IQ3_S,中间层FFN采用IQ2_M量化。这种激进的量化策略下带来了一定的能力退化,尤其是在指令遵循上有一定的问题,而且在我本地实测上容易出现死循环问题。评价为一般,网上对该网红量化模型的测评视频很多,我就不过多测试了。




下载链接

huggingface

modelscope)



2.2 Unsloth UD-Q3_K_XL


约等效量化3.8bpw,每一层都采用不同的量化策略,算是最均衡的一档模型,也是我测试最多的一款模型。Unsloth的这版非常吃KV Cache的精度,具体见第三章的测评案例。




下载链接

huggingface

modelscope



2.3 Apex I-mini


等效4.0bpw,质量上乘的一款量化,作者对每一层的量化敏感度进行测试,该量化版本可以配合tb3+tb3开启约110k的上下文。具体测评案例见第三章。




下载链接

huggingface

[modelscope]( Qwen3.8-27B-APEX-GGUF · 模型库)



3.测试案例


我对unsloth的量化版本与apex i-mini的量化版本分别进行了一定的案例测试(主要是unsloth的版本)。

测试用例来自:https://linux.do/t/topic/286836


3.1 鹈鹕骑自行车



创建一个HTML, 内容是SVG绘制一个鹈鹕骑自行车的2D动画



unsloth(tb3+tb3):



缺少腿部IK,但动画逻辑完整。


unsloth(tb4+tb3):



具备完整的腿部IK,但少了一部分踏板。


apex(q4+q4):



应该是质量上较好的一版但是腿部有点没对齐。


3.2黑洞



使用web技术制作一个图形化预览黑洞效果的网页,要求外观和样式要类似于电影《星际穿越》里面的黑洞卡冈图雅。黑洞背景要有其他星星,黑洞要有跟电影里类似的吸积盘效果和引力透镜效应。用户可以用鼠标旋转观察,并且在侧边栏可以调整相关参数体现不同参数黑洞的不同效果。你可以使用你认为必要的技术来实现这个网页,既要保证性能也要完成必要的效果。可以引入你觉得必要的第三方图形库比如three.js等等之类。



unsloth(tb3+tb3):



第一轮是遇到了一些bug(),单独提示有bug后自行修复。

下面是第一版的时候gpt的诊断报告:



这是它自己第二轮分析到的bug


unsloth(tb4+tb3):

一轮直出


3.3 天气卡片



你是 Apple Inc 的 UI 设计师,以 iOS 18 的设计风格做一个带有动画效果的天气卡片,要求是使用 HTML、CSS 和基础 JavaScript,使用横板天气页面(拥有 4 个天气卡片 (晴天,大风,暴雨,暴雪))。应足够美观,实现一定的交互效果。



unsloth(tb3+tb3):


3.4 上下文召回(90k上下文):


具体见本章开头该佬友收录题库的幻觉第一个needle用例。

最终unsloth(tb3+tb3)版本 90k上下文上做到了100%召回。


3.5逻辑题


糖果问题:



在一个黑色的袋子里放有三种口味的糖果,每种糖果有两种不同的形状(圆形和五角星形,不同的形状靠手感可以分辨)。数量如下:苹果味圆形7、桃子味圆形9、西瓜味圆形8;苹果味五角星7、桃子味五角星6、西瓜味五角星4。参赛者需在活动前决定摸出的糖果数目。问:最少取出多少个糖果,才能保证手中同时拥有不同形状的苹果味和桃子味的糖?



unsloth(tb3+tb3):


同时考虑到了21/29两种情况


父女遗传问题



有一天,一个女孩参加数学考试只得了 38 分。她心里对父亲的惩罚充满恐惧,于是偷偷把分数改成了 88 分。她的父亲看到试卷后,怒发冲冠,狠狠地给了她巴掌,怒吼道:" 你这 8 怎么一半是绿的一半是红的,你以为我是

傻子吗?" 女孩被打后,委屈地哭了起来,什么也没说。

过了一会儿,父亲突然崩溃了。

请问:这位父亲为什么过一会儿崩溃了?他意识到了什么?



unsloth(tb3+tb3):



一次答对


洗车问题



我想洗车,离洗车店只有50米,你建议我开车去还是走去



unsloth(tb3+tb3):


4.部署


推荐在wsl环境下部署,以50系N卡为例。

step.1 安装wsl2(powershell)


wsl --install -d Ubuntu-24.04

step.2 安装cuda12.8


sudo apt update
sudo apt install -y cuda-toolkit-12-8

step.3 安装编译所需的依赖


sudo apt update
sudo apt install -y git cmake ninja-build build-essential

step.4 拉取turboquant+mtp特性的llama.cpp分支


cd ~
git clone https://github.com/TheTom/llama-cpp-turboquant.git
cd llama-cpp-turboquant

git checkout feature/turboquant-kv-cache
git pull

amd版本可以采用以下链接版本:




仓库地址

GitHub - Pascal-SAPUI5/llama.cpp-turboquant: TurboQuant KV-cache quantization for AMD ROCm — 3-4 bit KV cache with <10% overhead (Zandieh et al., ICLR 2026) · GitHub



step.5 编译llama.cpp


rm -rf build

cmake -S . -B build -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DGGML_CUDA=ON \
-DCMAKE_CUDA_ARCHITECTURES=120

cmake --build build \
--target llama-server llama-cli llama-bench \
-j $(nproc)

nproc可以替换具体的数字。


step.6检查是否编译完成


./build/bin/llama-server --version
./build/bin/llama-cli --list-devices

5.llama.cpp的server-cli启动参数解读


以一下一套启动cli为例:


./build/bin/llama-server \
-m "~/models/ruby/Qwen3.8-27B-UD-Q3_K_XL/Qwen3.8-27B-UD-Q3_K_XL.gguf" \ #模型路径
#--mmproj “路径” \ 可选加载视觉编码器
-c 131072 \ #上下文长度
-ngl 99 \ # n-gpu-layer gpu负责多少层,直接拉满即可
-ctk turbo3 \ #k量化方案 建议tb4,tb3测下来也还可以
-ctv turbo3 \ #v量化方案,建议tb3
-fa on \ #开启快速注意力,可以加速模型推理速度
--no-mmap \ # 不启用mmap内存映射,模型直接在显存里不需要内存映射
--no-host \ # 不把模型放入内存,不需要存入内存
--cache-ram 0 \ # 这个也是不让模型放入内存
-np 1 \ # 一次最多多大并发,若np=2 则单个会话最大上下文=c/np,即会变成2个64K
-b 1024 \ #编码时每次的批处理大小,越大越吃显存但会加快编码速度(非推理速度)
-ub 512 \ #每次送入gpu计算图大小,一般设为 b的1/2 或1/4
# --spec-type draft-mtp \ 可开启mtp投机解码
# --spec-draft-n-max 2 \ 必须先开启mtp才能使用,每次预测多少个token
--jinja \ #在Chat模式下启用Jinja模板引擎,约束模型(可通过修改模板减少长思考问题,具体见github)
--host 0.0.0.0 \ #地址
--port 18080 #端口

6.作为本地llm的api


llama.cpp支持将llm转换成本地api。

具体而言只需要按照第五章的指令进行启动,当后端出现下图的提示时就可以通过该url进行连接了。默认的llama.cpp后端不需要密钥,也可以自行设定(就不细致展开了)


7. 结语


随着llm的发展,本地模型也会越来越强,tb3+tb3的unsloth q3版本体感有sonnet4.6的水平。16g显存的显卡可能上下文128K还不够用,但是智力和指令遵循层面已经完全合格。若想体验量化精度更佳的本地模型,可以使用显存更高的显卡。

btw:有挑战才有乐趣


最后附上我本机的实测显存占用截图(tb3+tb3 131072上下文):

































类型 区间 速度
prefill 全长度 1400t/s
decode 0-32K 45->40t/s
decode 32K-64K 40->38t/s
decode 64K-128K 38->26t/s

最新回复 (9)
  • ℰ𝓉𝒽𝒶𝓃2𝒫ℯ𝓇𝓉𝒽 08-23 10:58
    1

    正好之前买了一张4060ti 16g,部署来玩玩

  • Ruby 楼主 08-23 11:09
    2

    可以试试,效果不错,如果上下文长度压缩一下,开mtp提速的话应该能有30多t/s

  • cknights 08-23 11:10
    3

    qwen3.8 27b有个问题就是他会不经允许调用环境里任何能调用的东西,他甚至把我其他中转站里的api调用帮他干活

  • 辣椒炒肉 08-23 11:14
    4

    佬,我是4060ti 16gb,上下文只能开46k,多了就爆显存

    IQ2_M

    是不是没有在wsl里面部署的原因

  • upya 08-23 11:16
    5

    佬,这个几个对话就没了吧?如果压缩上下文的话,能不能将就着用用

  • Irovin 08-23 11:18
    6

    我的OMLX 27B 4Bit模型,喜欢骑车放开双手,和爱摸头~





    总结

    <!DOCTYPE html>
    <html lang="zh-CN">
    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>鹈鹕骑行 · 海风道</title>
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;700&family=ZCOOL+KuaiLe&display=swap" rel="stylesheet">
    <style>
    :root{
    --ink:#1E3A47; --paper:#FFF6E0; --red:#E8483F; --amber:#FFB020; --sea:#1E8F9C;
    }
    *{ box-sizing:border-box; }
    html,body{ height:100%; }
    body{
    margin:0; overflow:hidden; background:#2E9CCB;
    font-family:'Noto Sans SC',system-ui,sans-serif; color:var(--ink);
    -webkit-user-select:none; user-select:none;
    }
    svg#scene{ position:fixed; inset:0; width:100%; height:100%; display:block; }

    .plaque{
    position:fixed; background:var(--paper); border:3px solid var(--ink);
    border-radius:16px; box-shadow:7px 8px 0 rgba(30,58,71,.28);
    padding:14px 18px 12px; animation:drop .8s cubic-bezier(.2,1.4,.4,1) backwards;
    }
    @keyframes drop{ from{ opacity:0; transform:translateY(-26px) rotate(var(--rot,0deg)); }
    to { opacity:1; transform:translateY(0) rotate(var(--rot,0deg)); } }

    #title{ top:20px; left:20px; --rot:-2deg; padding:14px 22px 12px; }
    #title h1{
    font-family:'ZCOOL KuaiLe',cursive; font-weight:400; margin:0;
    font-size:clamp(34px,5.2vw,56px); line-height:1; letter-spacing:.06em;
    color:var(--red); text-shadow:2.5px 2.5px 0 var(--amber);
    }
    #title .row{ display:flex; align-items:center; gap:10px; margin-top:6px; }
    #title .zh{ font-weight:700; font-size:clamp(12px,1.4vw,15px); letter-spacing:.24em; }
    #title .en{ font-size:10px; letter-spacing:.32em; font-weight:700; opacity:.55; }
    #title .fish{ width:44px; flex:none; }

    #hud{ right:20px; bottom:20px; --rot:1.4deg; min-width:220px; }
    #hud .speed{ display:flex; align-items:baseline; gap:6px; }
    #hud .speed b{ font-family:'ZCOOL KuaiLe',cursive; font-weight:400; font-size:46px; line-height:1; color:var(--sea); }
    #hud .speed span{ font-size:12px; font-weight:700; letter-spacing:.2em; opacity:.6; }
    #hud .hint{ margin:8px 0 10px; font-size:12px; line-height:1.6; opacity:.85; }
    #hud kbd{ font-family:inherit; border:1.5px solid var(--ink); border-bottom-width:3px; border-radius:6px; padding:0 6px; background:#fff; font-size:11px; }
    .chips{ display:flex; gap:10px; }
    .chip{
    flex:1; font-family:'ZCOOL KuaiLe',cursive; font-size:17px; letter-spacing:.12em;
    padding:9px 0; border-radius:12px; cursor:pointer;
    border:3px solid var(--ink); background:#EAF4EF; color:var(--ink);
    transition:transform .15s, background .2s, color .2s, box-shadow .15s;
    }
    .chip:hover{ transform:translateY(-3px); box-shadow:0 5px 0 rgba(30,58,71,.2); }
    .chip[aria-pressed="true"]{ background:var(--amber); }
    .chip b{ display:block; font-family:'Noto Sans SC'; font-weight:700; font-size:9px; letter-spacing:.3em; opacity:.6; }

    #credit{ position:fixed; left:20px; bottom:16px; font-size:11px; letter-spacing:.18em;
    color:#fff; opacity:.75; background:rgba(30,58,71,.28); padding:5px 12px;
    border-radius:999px; }

    #rider{ cursor:pointer; }
    @media (max-width:640px){
    #credit{ display:none; }
    #hud{ left:20px; right:20px; bottom:14px; }
    }
    </style>
    </head>
    <body>

    <svg id="scene" viewBox="0 0 1600 900" preserveAspectRatio="xMidYMid slice"
    role="img" aria-label="一只白鹈鹕在海边公路上骑自行车的动画场景">
    <defs>
    <linearGradient id="gSky" x1="0" y1="0" x2="0" y2="1">
    <stop offset="0" stop-color="#2E9CCB"/>
    <stop offset=".58" stop-color="#7CD6E5"/>
    <stop offset=".88" stop-color="#D8F3E2"/>
    <stop offset="1" stop-color="#FFE9B3"/>
    </linearGradient>
    <linearGradient id="gSea" x1="0" y1="0" x2="0" y2="1">
    <stop offset="0" stop-color="#127387"/><stop offset="1" stop-color="#37AEB5"/>
    </linearGradient>
    <radialGradient id="gSun">
    <stop offset="0" stop-color="#FFF3B0"/>
    <stop offset=".6" stop-color="#FFD447"/>
    <stop offset="1" stop-color="#FFD447" stop-opacity="0"/>
    </radialGradient>
    <radialGradient id="gGround">
    <stop offset="0" stop-color="#0B1B26" stop-opacity="0"/>
    <stop offset=".72" stop-color="#0B1B26" stop-opacity="0"/>
    <stop offset="1" stop-color="#0B1B26" stop-opacity=".35"/>
    </radialGradient>

    <g id="cloud" fill="#FFF" opacity=".92">
    <ellipse cx="0" cy="0" rx="58" ry="24"/>
    <ellipse cx="48" cy="6" rx="44" ry="20"/>
    <ellipse cx="-46" cy="8" rx="40" ry="18"/>
    <ellipse cx="10" cy="-16" rx="38" ry="20"/>
    </g>
    <g id="palms">
    <g stroke="#2F7D4F" stroke-width="7" stroke-linecap="round" fill="none">
    <path d="M820 692 C826 664 818 636 830 610"/><path d="M846 692 C850 668 844 640 852 616"/>
    </g>
    <g stroke="#3FA05F" stroke-width="8" stroke-linecap="round" fill="none">
    <path d="M830 610 C800 596 772 598 748 612"/><path d="M830 610 C812 584 792 574 766 572"/>
    <path d="M830 610 C834 580 848 562 872 556"/><path d="M830 610 C856 588 884 584 908 592"/>
    <path d="M830 610 C852 606 874 614 890 630"/>
    <path d="M852 616 C830 606 810 608 792 618"/><path d="M852 616 C856 594 868 580 886 574"/>
    <path d="M852 616 C872 600 894 598 914 606"/>
    </g>
    </g>
    <g id="buoys" fill="#FF6B5E">
    <circle cx="250" cy="646" r="5"/><circle cx="520" cy="646" r="5"/><circle cx="1180" cy="646" r="5"/>
    </g>
    <g id="waveSet" stroke="#CFF3EE" stroke-width="4" stroke-linecap="round" fill="none">
    <path d="M80 604 q22 -10 44 0 q22 10 44 0"/><path d="M430 588 q22 -10 44 0 q22 10 44 0"/>
    <path d="M880 622 q22 -10 44 0 q22 10 44 0"/><path d="M1240 596 q22 -10 44 0 q22 10 44 0"/>
    <path d="M660 656 q22 -10 44 0 q22 10 44 0"/><path d="M1480 648 q22 -10 44 0"/>
    </g>
    <g id="bush">
    <circle cx="0" cy="-20" r="24" fill="#2F7D4F"/><circle cx="24" cy="-26" r="20" fill="#3FA05F"/>
    <circle cx="-22" cy="-24" r="18" fill="#3FA05F"/><circle cx="8" cy="-34" r="16" fill="#4CBA6E"/>
    </g>
    <g id="lamp">
    <rect x="-5" y="-205" width="10" height="205" rx="5" fill="#2A3B47"/>
    <rect x="-30" y="-228" width="60" height="24" rx="8" fill="#2A3B47"/>
    <circle cx="0" cy="-216" r="8" fill="#FFE08A"/>
    </g>
    <g id="sign">
    <rect x="-6" y="-150" width="12" height="150" rx="4" fill="#8B5A2B"/>
    <rect x="-68" y="-206" width="136" height="58" rx="10" fill="#1F7A5C" stroke="#FFF" stroke-width="3"/>
    <text x="0" y="-166" text-anchor="middle" font-family="ZCOOL KuaiLe, Noto Sans SC" font-size="27" fill="#FFF">海风道</text>
    </g>
    </defs>

    <!-- 天空 / 太阳 -->
    <rect x="0" y="0" width="1600" height="726" fill="url(#gSky)"/>
    <g id="sun">
    <circle cx="1315" cy="165" r="118" fill="url(#gSun)"/>
    <circle cx="1315" cy="165" r="54" fill="#FFD447"/>
    <circle cx="1315" cy="165" r="54" fill="none" stroke="#FFB020" stroke-width="4" opacity=".5"/>
    </g>
    <g id="clouds"></g>

    <!-- 海 -->
    <rect x="0" y="556" width="1600" height="142" fill="url(#gSea)"/>
    <rect x="0" y="556" width="1600" height="3" fill="#FFF" opacity=".5"/>
    <g id="waveFar"><use href="#waveSet"/><use href="#waveSet" x="1600"/></g>
    <g id="waveNear"><use href="#waveSet"/><use href="#waveSet" x="1600"/></g>

    <!-- 远景视差层 -->
    <g id="far">
    <use href="#buoys"/><use href="#buoys" x="1600"/>
    <g>
    <ellipse cx="300" cy="562" rx="82" ry="20" fill="#3F7D52"/>
    <path d="M300 556 C294 522 297 492 300 470 L322 470 C325 492 328 522 322 556 Z" fill="#FDF6EA" stroke="#1E3A47" stroke-width="2.5"/>
    <path d="M295 540 h33 M297 512 h31" stroke="#E8483F" stroke-width="9"/>
    <rect x="291" y="448" width="40" height="24" rx="5" fill="#E8483F"/>
    <circle cx="311" cy="460" r="6" fill="#FFE08A"/>
    <path d="M291 448 l20 -12 20 12 Z" fill="#1E3A47"/>
    </g>
    <path d="M690 556 q45 -16 90 0 Z" fill="#3F7D52"/>
    <path d="M1330 556 q55 -20 110 0 Z" fill="#3F7D52"/>
    <g id="boat" stroke="#1E3A47" stroke-width="3">
    <path d="M1400 596 q52 20 104 0 l-14 20 h-76 Z" fill="#E8483F"/>
    <path d="M1452 594 V540" /><path d="M1452 542 l-42 46 h42 Z" fill="#FFF6E0"/>
    <path d="M1452 542 l30 40 h-30 Z" fill="#FFE08A"/>
    <path d="M1452 542 q12 4 20 0" fill="none" stroke="#FFB020" stroke-width="4"/>
    </g>
    </g>

    <!-- 中景视差层 -->
    <g id="mid"><use href="#palms"/><use href="#palms" x="1600"/></g>

    <!-- 沙滩 / 路面 -->
    <rect x="0" y="696" width="1600" height="34" fill="#F0DBA0"/>
    <path d="M0 706 h1600" stroke="#D9BC77" stroke-width="2" stroke-dasharray="4 14"/>
    <rect x="0" y="728" width="1600" height="172" fill="#454B57"/>
    <path d="M0 730 h1600" stroke="#E6E9EF" stroke-width="4" opacity=".6"/>
    <path id="lane" d="M0 806 H1600" stroke="#F4C64D" stroke-width="10" stroke-dasharray="64 82"/>

    <!-- 近景视差层 -->
    <g id="road">
    <use href="#bush" x="180" y="732"/><use href="#lamp" x="430" y="732"/>
    <use href="#sign" x="760" y="732"/><use href="#bush" x="1090" y="732"/>
    <use href="#lamp" x="1330" y="732"/><use href="#bush" x="1520" y="732"/>
    <use href="#bush" x="1780" y="732"/><use href="#lamp" x="2030" y="732"/>
    <use href="#sign" x="2360" y="732"/><use href="#bush" x="2690" y="732"/>
    <use href="#lamp" x="2930" y="732"/><use href="#bush" x="3120" y="732"/>
    </g>

    <ellipse cx="810" cy="812" rx="285" ry="16" fill="#10181F" opacity=".22"/>
    <rect id="vignette" x="0" y="0" width="1600" height="900" fill="url(#gGround)" pointer-events="none"/>

    <!-- 风速线 -->
    <g id="winds" stroke="#EAF7F2" stroke-width="6" stroke-linecap="round" fill="none" opacity="0">
    <path id="w1" d="M330 610 H530"/>
    <path id="w2" d="M270 672 H450"/>
    <path id="w3" d="M370 730 H600"/>
    </g>

    <!-- 车轮 -->
    <g id="wheelF" stroke-linecap="round">
    <circle cx="980" cy="740" r="70" fill="none" stroke="#1E2630" stroke-width="11"/>
    <circle cx="980" cy="740" r="56" fill="none" stroke="#F3F6F9" stroke-width="5"/>
    <g stroke="#B9C4CE" stroke-width="4">
    <line x1="980" y1="684" x2="980" y2="796"/><line x1="924" y1="740" x2="1036" y2="740"/>
    <line x1="940" y1="700" x2="1020" y2="780"/><line x1="1020" y1="700" x2="940" y2="780"/>
    </g>
    <circle cx="980" cy="740" r="8" fill="#1E2630"/>
    </g>
    <g id="wheelR" stroke-linecap="round">
    <circle cx="640" cy="740" r="70" fill="none" stroke="#1E2630" stroke-width="11"/>
    <circle cx="640" cy="740" r="56" fill="none" stroke="#F3F6F9" stroke-width="5"/>
    <g stroke="#B9C4CE" stroke-width="4">
    <line x1="640" y1="684" x2="640" y2="796"/><line x1="584" y1="740" x2="696" y2="740"/>
    <line x1="600" y1="700" x2="680" y2="780"/><line x1="680" y1="700" x2="600" y2="780"/>
    </g>
    <circle cx="640" cy="740" r="8" fill="#1E2630"/>
    </g>

    <!-- 车架 -->
    <g stroke-linecap="round">
    <path d="M640 740 L760 745" stroke="#1E2630" stroke-width="8"/>
    <g id="chainA" stroke="#2A323C" stroke-width="7" stroke-dasharray="7 7" fill="none">
    <path d="M760 727 L640 728"/><path d="M760 763 L640 752"/>
    </g>
    <circle cx="640" cy="740" r="12" fill="#1E2630"/><circle cx="760" cy="745" r="17" fill="#1E2630"/>
    <circle cx="760" cy="745" r="7" fill="#FFB020"/>
    <path d="M640 740 L718 560" stroke="#1E8F9C" stroke-width="11"/>
    <path d="M760 745 L722 562" stroke="#1E8F9C" stroke-width="13"/>
    <path d="M722 562 L952 588" stroke="#1E8F9C" stroke-width="12"/>
    <path d="M760 745 L952 588" stroke="#1E8F9C" stroke-width="14"/>
    <path d="M952 588 L980 740" stroke="#1E8F9C" stroke-width="10"/>
    <path d="M952 588 L938 528 Q934 506 952 502 L966 500" fill="none" stroke="#1E2630" stroke-width="9"/>
    <path d="M966 500 L978 498" stroke="#E8483F" stroke-width="12"/>
    </g>
    <g id="bottle" transform="rotate(15 820 712)">
    <rect x="810" y="696" width="20" height="34" rx="7" fill="#FF8B3D"/>
    <rect x="813" y="690" width="14" height="8" rx="3" fill="#1E2630"/>
    </g>

    <!-- 旗杆 -->
    <path d="M640 606 L614 512" stroke="#1E2630" stroke-width="6" stroke-linecap="round"/>
    <path id="flag" d="M614 512 l-44 11 44 11 Z" fill="#E8483F"/>

    <!-- 车座 + 腿 -->
    <path id="saddle" d="M688 546 q32 -12 60 -2 q-6 12 -34 12 q-26 0 -26 -10 Z" fill="#E8483F" stroke="#1E2630" stroke-width="3"/>
    <path id="legB" d="M735 545 L770 700 L760 745" fill="none" stroke="#EF8E1C" stroke-width="18" stroke-linecap="round" stroke-linejoin="round"/>
    <path id="legF" d="M735 545 L770 650 L760 745" fill="none" stroke="#FF9E2C" stroke-width="21" stroke-linecap="round" stroke-linejoin="round"/>

    <!-- 车篮与鱼 -->
    <g>
    <rect id="fishBody" x="986" y="524" width="34" height="17" rx="8.5" fill="#4C6E93"/>
    <path id="tail" d="M1018 532 l17 -12 v24 Z" fill="#4C6E93"/>
    <g fill="none" stroke="#2A3A52" stroke-width="2.5">
    <path d="M988 553 V584"/><path d="M1002 553 V584"/><path d="M1016 553 V584"/><path d="M1030 553 V584"/>
    <path d="M984 562 H1032"/><path d="M984 574 H1032"/>
    </g>
    <path d="M982 552 h52 v32 h-52 Z" fill="none" stroke="#8B5A2B" stroke-width="5"/>
    </g>

    <!-- 踏板(含脚蹼,脚本定位) -->
    <g id="pedalB">
    <rect x="-23" y="-5" width="46" height="10" rx="4" fill="#1E2630"/>
    <path d="M-20 -5 Q-22 -16 -11 -18 L17 -18 Q27 -16 20 -5 Z" fill="#EF8E1C"/>
    <path d="M-6 -18 l3 -8 M6 -18 l3 -8" stroke="#EF8E1C" stroke-width="4" stroke-linecap="round"/>
    </g>
    <g id="pedalF">
    <rect x="-23" y="-5" width="46" height="10" rx="4" fill="#1E2630"/>
    <path d="M-20 -5 Q-22 -16 -11 -18 L17 -18 Q27 -16 20 -5 Z" fill="#FF9E2C"/>
    <path d="M-6 -18 l3 -8 M6 -18 l3 -8" stroke="#FF9E2C" stroke-width="4" stroke-linecap="round"/>
    </g>

    <!-- 鹈鹕本体 -->
    <g id="pelican">
    <path id="tail" d="M668 440 Q624 452 600 484 L644 490 L616 514 L662 514 Z" fill="#F4EDDD" stroke="#D9CFB8" stroke-width="2"/>
    <path d="M672 444 C664 392 716 358 780 358 C848 358 888 400 882 452 C876 506 824 540 762 538 C706 536 680 492 672 444 Z"
    fill="#FDFBF4" stroke="#D9CFB8" stroke-width="3"/>
    <path d="M690 516 C730 538 820 538 858 512 C846 530 806 542 762 540 C724 538 700 528 690 516 Z" fill="#E9E0CB"/>
    <path d="M824 420 C846 356 902 330 936 336 C954 340 962 356 958 372 C954 388 938 396 920 394 C884 390 856 420 850 462"
    fill="none" stroke="#FDFBF4" stroke-width="34" stroke-linecap="round"/>
    <path id="crest" d="M906 300 q-24 -18 -44 -10 q22 -4 34 6 q-18 -14 -38 -12 q18 -2 30 6 Z" fill="#FDFBF4" stroke="#D9CFB8" stroke-width="2"/>
    <path id="head" d="M902 340 q0 -32 34 -32 q32 0 32 30 q0 30 -32 30 q-34 0 -34 -28 Z"
    fill="#FDFBF4" stroke="#D9CFB8" stroke-width="3"/>
    <path id="pouch" d="M956 354 C978 402 1034 420 1086 384 L1088 366 C1044 398 986 388 956 354 Z"
    fill="#F08A24" stroke="#C96A15" stroke-width="3"/>
    <path d="M952 348 C952 330 984 318 1024 322 C1058 326 1084 338 1094 350 C1102 358 1098 364 1088 366 C1046 352 992 344 952 348 Z"
    fill="#FF9E2C" stroke="#C96A15" stroke-width="3"/>
    <path d="M1088 366 q12 6 2 14" fill="none" stroke="#C96A15" stroke-width="4" stroke-linecap="round"/>
    <path d="M1046 354 q-2 12 -12 22" fill="none" stroke="#C96A15" stroke-width="3" stroke-linecap="round" opacity=".8"/>
    <circle cx="944" cy="320" r="10" fill="#FFF"/>
    <circle id="pupil" cx="948" cy="321" r="4.6" fill="#1E3A47"/>
    <path d="M932 311 l15 -7" stroke="#D9CFB8" stroke-width="3" stroke-linecap="round"/>
    <g id="wing"><path d="M764 402 C714 400 672 432 662 480 C656 510 668 542 694 550 C708 554 716 542 712 528 C734 536 752 522 756 500 C762 468 766 434 764 402 Z"
    fill="#F1E8D4" stroke="#D9CFB8" stroke-width="3"/>
    <path d="M690 470 q14 24 8 46 M714 452 q16 26 10 52 M738 436 q14 28 8 54"
    fill="none" stroke="#D9CFB8" stroke-width="2.5" stroke-linecap="round"/></g>
    </g>

    <g id="wingspan" pointer-events="all"><rect x="590" y="270" width="540" height="555" fill="transparent"/></g>

    <!-- 鸣叫效果 -->
    <g id="honkfx" opacity="0">
    <g id="honkArcs" stroke="#FFD447" stroke-width="6" stroke-linecap="round" fill="none">
    <path d="M1116 330 a26 26 0 0 1 0 -40"/><path d="M1134 342 a46 46 0 0 1 0 -64"/>
    <path d="M1152 354 a66 66 0 0 1 0 -88"/>
    </g>
    <text id="honkTxt" x="1128" y="292" font-family="ZCOOL KuaiLe, Noto Sans SC" font-size="56"
    fill="#FFF6E0" stroke="#1E3A47" stroke-width="6" paint-order="stroke"
    transform="rotate(-8 1128 292)">噗——!</text>
    </g>
    </svg>

    <header class="plaque" id="title" style="--rot:-2deg">
    <div class="row">
    <svg class="fish" viewBox="0 0 44 24" aria-hidden="true">
    <path d="M4 12 q8 -10 22 -10 q12 0 14 10 q-2 10 -14 10 q-14 0 -22 -10 Z" fill="#1E8F9C"/>
    <path d="M2 12 l-2 -8 8 8 -8 8 Z" fill="#E8483F"/>
    <circle cx="30" cy="9" r="2.2" fill="#FFF"/>
    </svg>
    <div>
    <h1>鹈鹕骑行</h1>
    <div class="row"><span class="zh">海风道 · 单车特快</span><span class="en">PELICAN CYCLING CO.</span></div>
    </div>
    </div>
    </header>

    <div class="plaque" id="hud" style="--rot:1.4deg">
    <div class="speed"><b id="kmh">14</b><span>KM/H</span></div>
    <p class="hint">点一下鹈鹕,听它鸣一声 <kbd>空格</kbd><br>悬停时它会朝你挥手</p>
    <div class="chips">
    <button class="chip" id="btnSlow" aria-pressed="true">慢悠悠<b>EASY</b></button>
    <button class="chip" id="btnFast" aria-pressed="false">冲刺<b>SPRINT</b></button>
    </div>
    </div>

    <div id="credit">纯 SVG + 原生 JS 动画 · 无依赖</div>

    <script>
    (function(){
    "use strict";
    var $ = function(id){ return document.getElementById(id); };
    var wheelR=$('wheelR'), wheelF=$('wheelF'), legF=$('legF'), legB=$('legB'),
    pedalF=$('pedalF'), pedalB=$('pedalB'), pelican=$('pelican'), wing=$('wing'),
    pouch=$('pouch'), flag=$('flag'), tail=$('tail'), honkfx=$('honkfx'),
    honkArcs=$('honkArcs'), honkTxt=$('honkTxt'), winds=$('winds'),
    boat=$('boat'), lane=$('lane'), chainA=$('chainA'),
    far=$('far'), mid=$('mid'), road=$('road'),
    waveFar=$('waveFar'), waveNear=$('waveNear'), clouds=$('clouds'), sun=$('sun');
    var w1=$('w1'), w2=$('w2'), w3=$('w3');

    var BB={x:760,y:745}, CR=34, HIP={x:735,y:545}, L1=118, L2=118;
    var D2R=Math.PI/180;
    var S=1, targetS=1, dist=0, th=0, tt=0, honkT=0, wingPhase=0, wavePh=0;
    var hover=false, lastK=14, lastF=-1, lastW=-1, lastC=-1, lastShade=0;

    var cloudData=[
    {x:180,y:110,s:1.1,v:14},{x:520,y:60,s:0.8,v:10},{x:900,y:150,s:1.3,v:17},
    {x:1250,y:80,s:0.9,v:12},{x:1520,y:190,s:0.7,v:9}
    ];
    while(clouds.firstChild) clouds.removeChild(clouds.firstChild);
    cloudData.forEach(function(c){
    var u=document.createElementNS('http://www.w3.org/2000/svg','use');
    u.setAttribute('href','#cloud'); c.el=u; clouds.appendChild(u);
    });

    function ik(hx,hy,ax,ay){
    var dx=ax-hx, dy=ay-hy, d=Math.hypot(dx,dy);
    var mx=ax, my=ay;
    if(d>L1+L2-1){ var m=(L1+L2-1)/d; mx=hx+dx*m; my=hy+dy*m; dx=mx-hx; dy=my-hy; d=Math.hypot(dx,dy); }
    if(d<1){ dx=1; dy=0; d=1; }
    var a=(L1*L1-L2*L2+d*d)/(2*d), h=Math.sqrt(Math.max(0,L1*L1-a*a));
    var mx2=hx+dx*a/d, my2=hy+dy*a/d;
    var px=-dy/d, py=dx/d;
    if(px<0){ px=-px; py=-py; }
    return {x:mx2+px*h, y:my2+py*h};
    }

    var AC=null;
    function honkSound(){
    try{
    AC = AC || new (window.AudioContext||window.webkitAudioContext)();
    if(AC.state==='suspended') AC.resume();
    var t0=AC.currentTime;
    var o=AC.createOscillator(), g=AC.createGain();
    var o2=AC.createOscillator(), g2=AC.createGain();
    o.type='sawtooth'; o2.type='square';
    o.frequency.setValueAtTime(330,t0); o.frequency.exponentialRampToValueAtTime(130,t0+0.3);
    o2.frequency.setValueAtTime(336,t0); o2.frequency.exponentialRampToValueAtTime(133,t0+0.3);
    g.gain.setValueAtTime(0.0001,t0);
    g.gain.exponentialRampToValueAtTime(0.2,t0+0.03);
    g.gain.exponentialRampToValueAtTime(0.0001,t0+0.34);
    g2.gain.value=0.3;
    o.connect(g); o2.connect(g2); g2.connect(g); g.connect(AC.destination);
    o.start(t0); o2.start(t0); o.stop(t0+0.36); o2.stop(t0+0.36);
    }catch(e){}
    }
    function honk(){ honkT=1; honkSound(); }

    var btnSlow=$('btnSlow'), btnFast=$('btnFast');
    function setMode(s){
    targetS = s;
    btnSlow.setAttribute('aria-pressed', s===1 ? 'true':'false');
    btnFast.setAttribute('aria-pressed', s===3 ? 'true':'false');
    }
    btnSlow.addEventListener('click',function(){ setMode(1); });
    btnFast.addEventListener('click',function(){ setMode(3); });
    window.addEventListener('keydown',function(e){
    if(e.code==='Space'){ e.preventDefault(); honk(); }
    if(e.key==='1') setMode(1);
    if(e.key==='2') setMode(3);
    });
    var rider=$('wingspan');
    rider.addEventListener('pointerdown',function(e){ e.preventDefault(); honk(); });
    var scene=$('scene');
    scene.addEventListener('pointerenter',function(){ hover=true; });
    scene.addEventListener('pointerleave',function(){ hover=false; });

    if(window.matchMedia && matchMedia('(prefers-reduced-motion: reduce)').matches){
    S=0.5; targetS=0.5; btnSlow.setAttribute('aria-pressed','false');
    }

    var last=performance.now();
    function frame(now){
    var dt=Math.min(0.05,(now-last)/1000); last=now;
    S += (targetS-S)*Math.min(1,dt*2.2);
    tt += dt*(0.5+0.5*Math.min(S,2));
    var ground = (90+200*S)*dt;
    dist += ground;
    var cad = (70+150*S)*dt;
    th = (th + cad)%360;
    if(honkT>0) honkT=Math.max(0,honkT-dt*2.4);

    var bob = Math.sin(th*D2R*2)*3.2;
    if(honkT>0) bob -= Math.sin(honkT*22)*7*honkT;

    far.setAttribute('transform','translate('+(-(dist*0.18)%1600).toFixed(1)+' 0)');
    mid.setAttribute('transform','translate('+(-(dist*0.5)%1600).toFixed(1)+' 0)');
    road.setAttribute('transform','translate('+(-(dist*1.3)%1600).toFixed(1)+' 0)');
    lane.setAttribute('stroke-dashoffset',(-dist*1.3).toFixed(1));
    wavePh -= ground*0.22;
    waveFar.setAttribute('transform','translate('+(-wavePh%250).toFixed(1)+' 0)');
    waveNear.setAttribute('transform','translate('+(-wavePh*1.5%250).toFixed(1)+' 0)');

    cloudData.forEach(function(c){
    c.x += (c.v+28*S)*dt;
    if(c.x>1750) c.x=-320;
    c.el.setAttribute('transform','translate('+c.x.toFixed(1)+' '+c.y.toFixed(1)+') scale('+c.s+')');
    });
    var sr=(now*0.004)%360;
    if(Math.round(sr)!==lastShade){ lastShade=Math.round(sr); sun.setAttribute('transform','rotate('+sr.toFixed(1)+' 1315 165)'); }

    var thd=th.toFixed(1);
    wheelR.setAttribute('transform','rotate('+thd+' 640 740)');
    wheelF.setAttribute('transform','rotate('+thd+' 980 740)');
    chainA.setAttribute('stroke-dashoffset',(-th*0.55).toFixed(1));

    var hipY=HIP.y+bob;
    pelican.setAttribute('transform','translate(0 '+bob.toFixed(2)+') rotate('+(bob*0.4).toFixed(2)+' 760 540)');
    var legT=[0,180];
    for(var i=0;i<2;i++){
    var a=(th+legT[i])*D2R;
    var ax=BB.x+CR*Math.cos(a), ay=BB.y+CR*Math.sin(a);
    var k=ik(HIP.x,hipY,ax,ay);
    var p=(i===0?legF:legB);
    p.setAttribute('d','M'+HIP.x+' '+hipY.toFixed(1)+' L'+k.x.toFixed(1)+' '+k.y.toFixed(1)+' L'+ax.toFixed(1)+' '+ay.toFixed(1));
    var pd=(i===0?pedalF:pedalB);
    pd.setAttribute('transform','translate('+ax.toFixed(1)+' '+ay.toFixed(1)+') rotate('+((th+legT[i]+90)%360).toFixed(1)+')');
    }

    wingPhase += dt*(hover?14:4.2);
    var wa = (hover? -26+Math.sin(wingPhase)*30 : -16+Math.sin(wingPhase)*14);
    wing.setAttribute('transform','rotate('+wa.toFixed(1)+' 752 408)');

    if(honkT>0){
    var px=1022, py=384, sc=1+0.55*honkT;
    pouch.setAttribute('transform','translate('+px+' '+py+') scale('+sc.toFixed(3)+' '+(1+0.45*honkT).toFixed(3)+') translate('+(-px)+' '+(-py)+')');
    }else pouch.removeAttribute('transform');
    honkfx.setAttribute('opacity',honkT>0?honkT.toFixed(2):0);
    if(honkT>0){
    var gs=1+(1-honkT)*1.3;
    honkArcs.setAttribute('transform','translate(1120 326) scale('+gs.toFixed(2)+') translate(-1120 -326)');
    honkTxt.setAttribute('transform','rotate(-8 1128 292) translate(0 '+((1-honkT)*-24).toFixed(1)+')');
    }
    tail.setAttribute('transform','rotate('+(Math.sin(tt*3)*4).toFixed(1)+' 668 448)');
    flag.setAttribute('transform','rotate('+(Math.sin(tt*5+1)*12).toFixed(1)+' 614 512)');
    boat.setAttribute('transform','translate(0 '+(Math.sin(tt*0.9)*6).toFixed(1)+') rotate('+(Math.sin(tt*0.7)*3).toFixed(1)+' 1452 596)');

    var w = (S>1.7)? Math.min(1,(S-1.7)/1.3) : 0;
    if(Math.round(w*100)!==lastW){
    lastW=Math.round(w*100);
    winds.setAttribute('opacity',(w*0.85).toFixed(2));
    w1.setAttribute('stroke-dasharray','70 90'); w1.setAttribute('stroke-dashoffset',(-dist*3%160).toFixed(0));
    w2.setAttribute('stroke-dasharray','50 110'); w2.setAttribute('stroke-dashoffset',(-dist*3.6%160).toFixed(0));
    w3.setAttribute('stroke-dasharray','90 70'); w3.setAttribute('stroke-dashoffset',(-dist*2.8%160).toFixed(0));
    }
    var km=Math.round(8+6*S*S);
    if(km!==lastK){ lastK=km; $('kmh').textContent=km; }
    requestAnimationFrame(frame);
    }
    requestAnimationFrame(frame);
    })();
    </script>
    </body>
    </html>


  • Fxx00 08-23 11:21
    7

    很好,把逻辑题一次性列出来了,省得我一个个去找了

  • wa393 08-23 11:31
    8

    原来这么激进的量化还能保持这种性能吗^-^

    单独用 TurboQuant 分支还是要麻烦了,毕竟主线也默认启用了 KV Rotation。

    如果还想压榨一下上下文长度可以手动把 FFN 放到内存里,对速度影响不会太大https://linux.do/t/topic/2684842/22?u=wa39

  • 余梦 08-23 11:37
    9

    大佬的文章很详细,那么问题来了,显卡、内存买不起怎么办

* 帖子来源Linux.do
返回