news 2026/11/3 10:47:09

10 月 27 日那个检查点,把「一次生成成功」从碰运气变成了常态

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
10 月 27 日那个检查点,把「一次生成成功」从碰运气变成了常态

10 月 27 日那个检查点,把「一次生成成功」从碰运气变成了常态

做 3D 代码生成这件事,评估口径很容易跑偏。社区里几乎所有的讨论都停留在“这图好看不好看”“这次生成的樱花树有没有氛围”——但对于一个输出「单文件 HTML + 内联 JavaScript + 远程加载 Three.js」的模型来说,好看只是必要条件,能不能一次跑通才是可用性本身。

仓库 README.md 在 10 月 27 日检查点这一节里给出的,恰恰是这句最不像营销话、也最难造假的表述:一次生成成功率(one-shot success)显著提升,场景整体质量同步提升,部分场景仍需重试。没有百分比,没有 benchmark,只有一张罗马场景在 10 月 25、26、27 三个预览版本下的对比图。

这篇就围绕这个检查点,把三件事讲清楚:为什么“一次成型率”是这类任务唯一诚实的指标;仓库里有哪些可验证的差异证据;以及怎么用仓库自带的参数,在你自己的机器上把这个数字量出来——顺便说清“一次成型之后,剩下的活到底还有多少”。

一、“一次成型”为什么比“平均质量”更重要

Three.js 场景生成是一个**全或无(all-or-nothing)**的产物形态,这一点由交付方式决定,而不是由模型决定:

  • 产物是一个自包含 HTML 文件,用户双击就打开。没有编译期,没有类型检查,没有任何机会在“出错之前”拦住问题;
  • 错误被完整推迟到运行时。一个拼错的变量名、一个没定义的THREE.Something、一次addons导入失败,浏览器不会给你降级方案,你得到的就是一块黑屏或一片空白;
  • 渲染循环里一旦抛异常并中断requestAnimationFrame链,画面会冻结在第一帧,连“部分可见”的安慰都没有。

这就意味着并不存在“70 分作品”这种东西。一个平均观感 90 分、但每三次有一次黑屏的模型,实际可用性远低于平均观感 80 分、但十次十次能跑起来的模型。而“平均质量”这个指标会严重掩盖这一点,因为它把黑屏排除在统计之外了。

成本结构也在推同一个方向。按 README.md 的系统提示与示例提示词,模型输出的是一个包含完整场景构建代码、光照、相机与交互逻辑的文件,训练数据的每条样本还带完整 thinking trace(数据集共 4,146 条,3,933 训练 / 213 验证)。也就是说:一次采样是重成本,重试是成倍的重成本,而人工修正的成本最高——你得先读懂它的变量命名习惯,才能改对。所以取pass@1(单次采样成功率)而不是pass@k,不是学术洁癖,而是生产口径。

二、检查点前后的可观测差异,以及仓库里能查到的证据

先看 README 原文说了什么,这决定了哪些结论能说、哪些不能说:

项目10 月 27 日检查点后的状态
权重覆盖范围Q4_K_M与Q8_0两个 GGUF 均包含更新后的检查点,旧版本需重新下载
一次成功率“improved substantially”(显著提升)
场景整体质量同步提升
残余问题部分场景仍可能需要重试或修正
生成参数保持不变
顺带修复部分用户反馈的“层级相关”问题(layer-related issue)

其中最有信息量的一行是“生成参数保持不变”。这句话排除了一个最常见的误判:提升不是靠调采样换来的。temperature、top-p、top-k、repeat penalty 在检查点前后完全一致,所以行为差异只能来自权重本身。这比任何“我们优化了解码策略”的说法都更可信,因为它可被外部证伪——你自己拿旧文件和新文件跑同一组提示词就能看到。

第二个证据是罗马场景的三日对比图,它把“质量演化”做成了同一提示词、同一视角下的横向切片:

这里有个容易被忽略的细节,值得单独点出来:README 里画廊的十张成品图并不是 10 月 27 日之后的产物。这十张图在仓库里以 SVG 形式内联了原始 PNG,PNG 的tEXtCreation Time元数据写得很清楚——九张拍摄于 2026 年 10 月 25 日 02:11–02:35(-03 时区),日落村庄那张更早,是 10 月 23 日 16:14。

也就是说,画廊证明的是检查点之前的质量下限已经可用;它不能用来支撑“10 月 27 日之后更好”这个论断——那个论断只有roma3.png和 README 的定性描述在支撑。这个区分很重要:一个认真的技术判断,必须知道自己在引用哪一段证据。

三、把一次成型率量出来:直接沿用生产线参数

官方没有公布数字,所以这件事只能自己做。好消息是仓库把该固定的一切都固定下来了,你只需要搭一个最小抽样台。

第一步:锁定参数。参数直接抄 generation_config.json,不要自己发明:

参数仓库默认值备注
do_sampletrue默认就是采样,不是贪心
temperature0.6README 接受区间 0.6–0.8
top_p0.95README 接受区间 0.90–0.95
top_k20README 接受区间 20–40
repetition_penalty1.0即关闭重复惩罚,不要“顺手加一点”
eos_token_id[248046, 248044]两个终止符

这里有两个坑必须提前说明,否则你的统计会统计到运行时而不是模型:

  1. repetition_penalty是 1.0,也就是不启用。很多本地推理前端会自作主张给代码任务设 1.05–1.1。对自然语言这是去重,对长代码文件这是改名机——它会把重复出现的box、mesh、group惩罚成box2、meshB,直接制造出 README「Known issues」里点名的那类重复/错位变量。统计一次成型率时把它固定为 1.0。
  2. eos_token_id是两个 token。如果你的运行时只在一个终止符上停,输出会多吐一段,或者把<|endoftext|>之后的残片一起塞进 HTML。<script>之后多出来的东西里只要有一个非法字符,页面就整块挂掉——这是harness 的 bug,不是模型的 bug。

第二步:固定提示词与种子。用 README 里现成的系统提示,它本身就是最强的结构约束:

Use only the Three.js core ES module from https://cdnjs.cloudflare.com/ajax/libs/three.js/0.160.0/three.module.min.js. Do not import addons or legacy examples/js paths; implement any camera controls directly inside the HTML.

然后是采样脚本。下面这段跑在 LM Studio / llama.cpp 的 OpenAI 兼容端点上,同一提示词 × 不同 seed,输出全部落盘:

# sample_once.py —— 固定参数,只让 seed 变化 import json, pathlib, requests SYSTEM = open("prompt/system.txt").read().strip() OUT = pathlib.Path("samples"); OUT.mkdir(exist_ok=True) def gen(tag, prompt, seed): r = requests.post("http://127.0.0.1:1234/v1/chat/completions", json={ "model": "qwen3.5-4b-3d-coder", "messages": [ {"role": "system", "content": SYSTEM}, {"role": "user", "content": prompt}, ], "temperature": 0.6, "top_p": 0.95, "top_k": 20, "repeat_penalty": 1.0, "seed": seed, "max_tokens": 16384, }, timeout=600).json() text = r["choices"][0]["message"]["content"] # 模型偶尔仍会带 markdown 围栏,剥掉后再当作 HTML 使用 if text.lstrip().startswith("```"): text = text.lstrip().split("\n", 1)[1].rsplit("```", 1)[0] (OUT / f"{tag}-seed{seed}.html").write_text(text, encoding="utf-8") return len(text) PROMPTS = { "canal": open("prompt/canal-lock.txt").read(), "atc": open("prompt/atc-tower.txt").read(), "aerie": open("prompt/dragonrider-aerie.txt").read(), } for tag, p in PROMPTS.items(): for seed in range(1, 6): print(tag, seed, gen(tag, p, seed))

第三步:判定。判“成功”不能靠肉眼看截图,否则你又会滑回主观评分。三个客观条件同时满足才算通过:

  • 没有pageerror,没有console.error(THREE.WebGLRenderer: Context Lost、xxx is not defined都会在这里露出来);
  • 页面里存在<canvas>且尺寸非零;
  • 画面不是纯色。用 Puppeteer 直接对 canvas 元素截图,再统计颜色多样性——纯黑屏和“只画清屏色”的失败样例会在这里被一键筛掉。
// check.js —— 对 canvas 截图做颜色多样性统计 const puppeteer = require('puppeteer'); const sharp = require('sharp'); async function check(file) { const browser = await puppeteer.launch({args: ['--use-gl=swiftshader']}); const page = await browser.newPage(); const errors = []; page.on('pageerror', e => errors.push(e.message)); page.on('console', m => m.type() === 'error' && errors.push(m.text())); await page.setViewport({width: 1280, height: 800}); await page.goto('file://' + file); await new Promise(r => setTimeout(r, 4000)); // 等待首帧与贴图/几何构建完成 const el = await page.$('canvas'); if (!el) { await browser.close(); return {ok: false, why: 'no canvas'}; } const {data, info} = await sharp(await el.screenshot()) .resize(160, 100).raw().toBuffer({resolveWithObject: true}); const uniq = new Set(); for (let i = 0; i < data.length; i += info.channels * 4) { // 抽稀 uniq.add((data[i] << 16) | (data[i + 1] << 8) | data[i + 2]); } await browser.close(); const ok = errors.length === 0 && uniq.size > 64; // 阈值按场景复杂度调 return {ok, why: errors[0] || `uniq=${uniq.size}`}; }

把 15 个样本的ok加起来除以 15,就是这一次运行的pass@1。关键在于同一批提示词、同一批 seed、同一套参数,分别在旧检查点和新检查点上各跑一遍——只有这样,你得到的才是“检查点差异”,而不是“我这次心情好,提示词写得更细”。

顺带一个必须记住的开关:LM Studio 里要关掉 multi-token prediction(MTP)。README 已经用加粗标了这条,但它给的是结论,不是原因。原因在仓库文件里,我们留到第五节说。

四、一次成型之后,剩下的活还有多少

README 的「Known issues」只有一句话,但信息量极大:

The bugs observed so far have generally involved duplicate variable names, rather than major structural problems.

翻译成工程语言:失败模式集中在“局部命名冲突”,而不是“结构建错”。这句话决定了返工的性质——你拿到的不是一堆需要重画的几何体,而是一份骨架正确、只需改几行的代码。这也是为什么“一次成型率”一旦上来,整体体验会产生质变:剩下的工作从“重新生成”变成了“修三行”。

但为什么一个专门训练的模型会反复栽在变量名上?把系统提示和交付形态放在一起看,答案是结构性的:

  • 禁止 addons、禁止旧版examples/js路径,意味着相机控制、轨道交互都得在同一个文件里手写。手写就意味着大量let/const声明挤在同一个顶层作用域里;
  • 自包含单文件,意味着不能拆模块、不能用命名空间隔离,所有函数共享一个巨大的作用域;
  • 场景规模大(针脚阵列、斗拱、栏杆立柱这类重复结构天然需要循环与工厂函数),意味着命名空间会被反复使用。

三者叠加,就是「作用域膨胀 × 长上下文」。所以这类 bug 不是"模型不够聪明",而是交付形态本身在惩罚命名。这也解释了一个看起来矛盾的现象:细节密度已经相当高的场景(比如前面那张插槽图)可以一次生成成功,而结构更简单的场景偶尔会因为一个重名的group直接黑屏。

在这个认知下,“剩下的活”可以分成四类,代价差别很大:

类别典型症状处理方式代价
阻塞级运行时错误xxx is not defined、重复声明、黑屏改一两行,重跑低,但必须有人看一眼
约束违规偷偷 import 了 addons / examples 路径删掉并补手写控制逻辑中,可能牵动相机
视觉级缺陷光照平、材质糊、构图拥挤调提示词(布局段落写细)中,需重新采样
性能级缺陷几何体数量爆炸导致帧率崩合并几何、减少 draw call高,建议人工重写该段

换句话说:10 月 27 日那个检查点解决的是第一类的发生率,而不是把四类全清了。README 那句“some scenes can still require another attempt or a correction”就是这个意思,而且它诚实地把“重试”和“修正”并列了——这两种成本本来就不是一回事。

五、一个 4B 凭什么撑住长文件:配置里的几个数字

打开 config.json,能读到比模型卡更有意思的东西。先看总量:

"metadata": { "total_parameters": 4539265536, "total_size": 9078531072 }

来自 model.safetensors.index.json。四点五亿?不,是 45.39 亿参数、9.08 GB(FP16),分四个分片。所以名字里的“4B”是基座规格,实际含视觉塔后是 4.54B——这个差异在你算显存预算时会体现出来。

再看层结构,这是它能吃下长文件的关键:

  • 32 层,其中 24 层是linear_attention,只有 8 层是full_attention,full_attention_interval: 4,也就是每四层插一个全注意力层。权重索引里的计数完全对得上:linear_attn张量 216 个(24 层 × 9),self_attn48 个(8 层 × 6)。
  • 全注意力层是num_attention_heads: 16/num_key_value_heads: 4,4:1 的 GQA;head_dim: 256。
  • partial_rotary_factor: 0.25,只有四分之一的维度加旋转位置编码;mrope_interleaved: true,mrope_section: [11, 11, 10]——这是多模态基座留下的时间/高/宽三分位置编码,在文本代码任务里主要退化为普通位置信号。
  • max_position_embeddings: 262144,即 256K。

算一笔账:每 token 每层 KV 是2 × 4 × 256 × 2B = 4 KB,只有 8 个全注意力层需要缓存,所以每 token 约 32 KB。跑满 256K 上下文理论上要 8 GiB KV——对一个 9 GB 的 FP16 权重来说这仍然不便宜,但相比 32 层全注意力(每 token 128 KB、满配 32 GiB)已经是数量级的差别。这就是“24 层线性注意力”在长代码生成场景里的真实价值:它让“一次吐完一个几千行的场景文件”在消费级硬件上成为可能,而不是一个理论指标。

然后是那个 MTP 陷阱的根因。text_config里明明白白写着:

"mtp_num_hidden_layers": 1, "mtp_use_dedicated_embeddings": false

但把 353 + 146 + 146 + 78 个张量名全过一遍,没有任何一个以mtp开头的键。也就是说,配置声明了 MTP 分支、权重文件里却没有对应的参数。运行时如果按 config 去实例化这一层并启用多 token 预测,它拿不到权重,行为就退化为乱码、截断或直接无响应。

社区里流传的“LM Studio 必须关 MTP”因此不是一个玄学偏方,而是配置与权重不一致时的必然结果,而且它和量化格式(Q4_K_M/Q8_0)毫无关系——两个 GGUF 都会中招,因为它们源自同一份权重与同一份配置。同理,如果你用transformers自己搭推理,看到architectures是Qwen3_5ForConditionalGeneration(一个条件生成类),也别急着去找 MTP 的加载逻辑——按 generation_config.json 的默认值走,反而最稳。

六、把“一次成型”变成常态的操作清单

把上面所有事实收敛成一份可以直接照做的清单:

  1. 参数照抄,不要发明。temperature 0.6、top_p 0.95、top_k 20、repetition_penalty 1.0。想探索就把 0.6–0.8 / 0.90–0.95 / 20–40 当区间,别把 repeat penalty 调到 1.05 以上。
  2. 关掉 MTP。无论 LM Studio 还是自建服务,只要你的运行时认 config 里的mtp_num_hidden_layers,就得关。这是配置与权重不一致导致的,不是模型缺陷。
  3. 系统提示保持那两句不动。锁死 Three.js 0.160.0 核心 ES 模块、禁止 addons、控制逻辑写在 HTML 内。它不是风格建议,而是把“import 失败”这一类整屏黑从可能性里删掉。
  4. 用户提示按“主题 + 布局关系 + 渲染风格”三段写。README 的示例就是这个套路的范本——先给对象,再把空间关系写成可执行的约束(“a broad sheltered yard connects water, shade, and feeding areas through accessible paths”),最后统一收口到渲染语言(“a deliberate 3D voxel model built from visible cubic blocks, with clear depth, readable silhouettes”)。第三段是最容易被省掉、也最影响一次成型率的一段。
  5. 不要关 thinking。训练数据里每条样本都带完整 thinking trace,说明推理链是分布的一部分。用 chat_template.jinja 时注意:enable_thinking=false会注入一个空的<think>\n\n</think>\n\n块,等于让模型在“没有推理过程”的形态下生成,这和它被训练的样子不一致。
  6. 失败要聚类,不要重试到底。记录每次失败的第一条pageerror,按错误类型分组。如果九成失败都是重名变量,说明你的瓶颈在提示词的空间描述或文件规模;如果失败集中在THREE.* is not defined,说明有人在偷偷用 addons——这两类问题的修法完全不同。

最后回到标题。10 月 27 日那个检查点值得被单独讨论,不是因为它把 3D 生成推到了什么新高度,而是因为它把一个几乎不可用的东西变成了可用的东西:当一次成型率足够高,你才敢把它放进“打开就能看”的工作流里,才敢让不懂 JavaScript 的人去写提示词。至于剩下的那部分活——重名变量、偶发的约束违规、需要人工收尾的性能问题——它们依然存在,而且短期内不会消失。诚实地说,这个模型的当前状态是“一次生成基本能跑,跑起来之后还需要一点收拾”,而这恰恰就是一个 4B 模型在 2026 年应该达到的位置。

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/3 10:46:55

memory __get_fault_gfp_mask

__get_fault_gfp_mask 是一个缺页异常处理路径中的辅助函数&#xff0c;用于为当前触发的缺页异常确定合适的内存分配标志&#xff08;GFP flags&#xff09;。它的核心逻辑是&#xff1a;如果 VMA 关联了文件&#xff0c;则使用该文件映射的 GFP 掩码&#xff1b;否则&#xf…

作者头像 李华
网站建设 2026/11/3 10:46:24

热水茶壶工作状态检测设计

热水壶工作状态检测热水器工作报警器 使用继电器线圈来检测电流 AD\Test\2026\November\TeaPortMEGA8CheckCurrrent.PcbDoc* 热水器工作状态检测 一、电路设计 在之前&#xff0c;利用继电器线圈来感应热水壶工作电流&#xff0c;用于水开报警模块。  这个呢我们为了简化整…

作者头像 李华
网站建设 2026/11/3 10:46:20

阿里云部署

一、部署任务&#xff1a;使用阿里云组件完成若依项目部署&#xff1a;1. ECS&#xff1a;部署 Nginx、Spring Boot。机器密码&#xff1a;Qwer12342. RDS MySQL&#xff1a;存储业务数据。3. Redis&#xff1a;缓存数据。密码&#xff1a;redis123准备工作&#xff1a;1.安装网…

作者头像 李华
网站建设 2026/11/3 10:45:02

第 9 集:任务超时了,为什么还在运行?

资深 Java 面试到 Agent 开发工程师 第 9 集 我自己做了一个 Java 并发小游戏《并发突围》&#xff0c;无广告、免费玩。点这里试玩。 接口返回“查询超时”&#xff0c;后台却还占着线程。原因是&#xff1a;get(timeout) 只结束等待&#xff0c;不会自动取消任务。要让工作…

作者头像 李华
网站建设 2026/11/3 10:44:37

深入探索 Python 核心函数与模块:从 ord() 到 dis

在编程这样的环境当中, 了解语言里面的核心函数还有模块这些内容, 确实能够让咱们的编程工作变得更为高效, 同时也能让代码的质量有明显提高, 这篇文章会把重点放在几个比较重要的函数以及模块上面去讨论, 具体来说就包括了ord这个函数、id这个函数还有dis这个模块, 并且还会利…

作者头像 李华
网站建设 2026/11/3 10:44:35

FindStar星图计划:STM32从一颗 LED 开始

星图 C8T6 01&#xff5c;从一颗 LED 开始&#xff1a;验证整条链路星图计划 C8T6 卷第 1 颗星。FindStar 寻星工作室 STM32F103C8T6 65 个项目&#xff0c;从这颗灯开始。为什么第一课永远是点灯 每个学单片机的人都被同一句话送进门&#xff1a;先让灯闪起来。 这话听起来…

作者头像 李华