既白 (@BaiBtw) header
既白 (@BaiBtw) profile picture

💻 游走于代码与链上世界 🟠 偶尔投机,长期创造 🕊️ 保持自由,保持清醒

区块链
Joined March 2024
Photo by 既白 (@BaiBtw)Photo by 既白 (@BaiBtw)Photo by 既白 (@BaiBtw)Photo by 既白 (@BaiBtw)Photo by 既白 (@BaiBtw)Photo by 既白 (@BaiBtw)Photo by 既白 (@BaiBtw)
Python 队列关闭了,join() 返回就代表任务全做完了吗? 读了 Python queue 英文文档,关闭队列时有两个很容易混在一起的概念:停止接收任务,以及完成已有任务。Queue.shutdown() 从 Python 3.13 加入。 1. 默认关闭:把已有任务收尾 q.shutdown() 之后,新 put() 会抛出 ShutDown,正在等待空位的生产者也会被唤醒并收到这个异常。 队列里已有的任务仍然可以 get() 出来处理。每个任务处理结束后调用 task_done(),未完成计数归零,join() 才会正常解除阻塞。队列取空后,get() 会抛出 ShutDown。 2. 立即关闭:允许放弃排队任务 q.shutdown(immediate=True) 会清空还留在队列里的任务,并从未完成计数中扣掉这些任务。计数变成零时,join() 就能返回。 例如,3 个任务都还在排队、没有任务正在处理,立即关闭可以让 join() 返回,即使这 3 个任务一个也没执行。已经被工作线程取走的任务,则不能当作队列清空时一起完成了。 3. 停机流程要讲清楚哪种结果算成功 需要处理完已有任务,就选择默认关闭,并让消费者处理 ShutDown 异常退出。允许丢弃待处理任务,才考虑立即关闭。 task_done() 是完成计数的通知,不会替业务验证结果。若任务失败需要重试,还得另行设计失败记录和重试逻辑。 看停机代码时,别只盯着 join() 有没有返回,也看看它是在什么关闭模式下返回的。 英文来源:docs.python.org/3/library/qu…
10
18
412
Python 里给对象注册清理回调,weakref.finalize 有个很容易写反的地方:回调不能把对象自己留住。 例如: weakref.finalize(obj, obj.cleanup) 看起来是“对象回收时,调用它的 cleanup”。问题在于,obj.cleanup 是绑定方法,会持有 obj。清理器又保存着这个方法,反而可能让 obj 一直无法被垃圾回收。 官方文档建议的方向,是让回调只拿到清理所需的独立信息。例如: weakref.finalize(obj, cleanup, resource_id) 这里仍要检查 cleanup、resource_id 等参数,是否直接或间接引用了 obj。只把代码换个写法,不一定真的断开了引用。 finalize 的几个行为也值得一起记住: ① 不需要自己保存返回的清理器。它不会只因你没保存这个返回值,就提前失效。 ② 回调最多执行一次。可以主动调用清理器;调用后它就变成 dead,后续再调用会返回 None,不会重复执行清理。 ③ detach() 会撤销一个仍存活的清理器,并返回原来的对象、函数和参数。它不是“立即清理”。 ④ atexit 默认是 True。程序正常退出时,仍存活的清理器按创建顺序的倒序调用。不能把它理解成任何退出方式都保证清理成功。 用一个简单例子看“最多一次”: import weakref class Job: pass job = Job() f = weakref.finalize(job, print, "cleanup") f() # 打印一次 cleanup f() # 不再打印 还有一个边界:垃圾回收触发的回调如果抛出异常,会写到标准错误,但不能向外传播。不要把关键业务的成功确认,寄托在这个回调上。 读这类生命周期代码时,可以顺着引用关系画一圈:谁保存了回调,回调又保存了谁。名字叫“弱引用”,不代表整条引用链都是弱的。 整理自 Python 官方英文文档,示例按文档行为简化: docs.python.org/3/library/we…
11
13
395
任务很多时,先别急着写一长串 if,先把「谁必须等谁」画清楚。 Python 标准库的 graphlib.TopologicalSorter 就是做这件事的:根据依赖关系,给出一个可行的先后顺序。它从 Python 3.9 起就有,不需要另装图算法库。 最容易写反的是输入格式: 字典的键是当前任务,值是它的前置任务,不是它后面要执行的任务。 例如,打包要等测试和文档完成,而测试和文档都要等构建完成: from graphlib import TopologicalSorter graph = { "build": (), "test": ("build",), "docs": ("build",), "package": ("test", "docs"), } order = tuple(TopologicalSorter(graph).static_order()) 这里 build 必须在前,package 必须在后。test 和 docs 之间没有相互依赖,所以拓扑顺序不一定只有一种;同一层任务的返回顺序可能受插入顺序影响。 使用时可以分成两种模式: 1. 只需要一份执行顺序 用 static_order()。它返回迭代器,可以转成 tuple 或 list。这种用法不要再手动调用 prepare()、done()。 2. 想把当前可运行的任务交给工作线程或进程 先 prepare(),再用 get_ready() 取出前置任务均已完成的节点。任务真正做完后,调用 done(node),后续依赖它的任务才可能就绪。 关键区别是:get_ready() 不是“已经完成”,done() 也不该在刚把任务交出去时就调用。排序器负责依赖状态,实际执行仍由你的代码安排。 还有三个边界: • 节点必须可哈希;同一节点多次 add(),依赖会合并,不是覆盖。 • prepare() 之后不能再往图里加节点。 • 完整的拓扑顺序要求图中没有有向环。有环会抛 CycleError;不过 prepare() 报错后,仍可通过 get_ready() 取出尚未被环阻塞的任务。 适合拿它整理构建步骤、数据处理阶段这类依赖关系。先分清依赖,再决定串行还是并行,代码会清楚很多。 英文原文:docs.python.org/3/library/gr…
6
199
Python 里的变量放在模块顶层,值就一定是全局共享的吗? ContextVar 是个容易混淆的例外:变量对象可以放在模块顶层,但它读写的是「当前上下文」里的值。 Python 官方文档用 contextvars 来管理这类状态。在异步代码里,它能减少把请求信息沿着每一层函数参数传下去的麻烦,也避免把本该属于当前上下文的状态混到别处。 可以分成三件事理解: 1. 声明变量,按上下文取值 from contextvars import ContextVar request_id = ContextVar("request_id", default="unknown") request_id.get() 取当前上下文里的值。当前没设置时,先看 get() 是否传了默认值,再看声明时的 default;两者都没有,就抛 LookupError。 2. 临时修改,记得恢复 token = request_id.set("req-42") try: print(request_id.get()) finally: request_id.reset(token) set() 返回的 token 记录了恢复所需的信息。reset(token) 恢复的是这次 set() 之前的值,不是一律清空。一个 token 不能重复用于恢复。 Python 3.14 起,token 还支持上下文管理器,可以写成: with request_id.set("req-42"): print(request_id.get()) 离开 with 块后会自动恢复。旧版本就用 try/finally。 3. 需要切换整份上下文时,用 Context copy_context() 复制当前上下文,官方文档注明复杂度是 O(1)。ctx.run(func) 会进入该上下文执行函数,结束时恢复原来的上下文;函数在里面对 ContextVar 做的修改留在 ctx 中。 两个边界值得记住: • ContextVar 应在模块顶层创建,不要放在闭包里临时造。Context 会强引用这些变量,可能影响回收。 • asyncio 原生支持上下文变量,不需要额外配置。官方示例把客户端地址放进 ContextVar,让处理该客户端的 Task 内部函数直接读取。 它解决的是「当前这段执行该读到哪份状态」,不是把一个普通全局变量换个名字。 英文原文:docs.python.org/3/library/co…
4
5
218
把一个很长的整数写成 JavaScript 的 Number,它可能在进入 JSON.stringify 之前就已经丢了精度。 MDN 介绍的 JSON.rawJSON(),给这类序列化问题留了一个入口:直接指定某个值在 JSON 文本里应该长什么样。 看这个例子: const id = JSON.rawJSON("12345678901234567890"); JSON.stringify({ id }); 输出是: {"id":12345678901234567890} 传入 rawJSON 的虽然是字符串,最终 JSON 里的 id 却是数字,不会再被包上一层引号。 几个边界要分清: 1. 接收的是合法 JSON 原始值 数字、字符串、布尔值、null 都可以。想表示 JSON 字符串,传入的文本本身也要带双引号,比如 JSON.rawJSON('"hello"')。 对象和数组不行。把 '{"a":1}' 或 '[1,2]' 交给它,会抛 SyntaxError。非法 JSON 也不会被放行。 2. 它保留文本,不会修复已经丢失的数字 如果先把大整数转成了 Number,再拿这个 Number 转字符串,精度问题已经发生。原始值需要一开始就用准确的字符串、BigInt 或高精度类型保存。 3. 发得准确,接收端也要接得住 另一端如果直接用普通 JSON.parse 读成 Number,仍然可能丢精度。 MDN 给的 JavaScript 处理方式,是在 reviver 里通过 context.source 取出原始数字文本,再按已知字段转成 BigInt。只对已经解析过的 value 做 BigInt 转换,可能已经晚了。 所以要先看接收方:支持高精度数字,可以传 JSON 数字;不支持,就约定传字符串,再按业务类型处理。 最后,部署前检查运行环境是否支持 JSON.rawJSON。它是序列化工具,不能让 JavaScript 的 Number 自动变成任意精度。 英文原文:developer.mozilla.org/en-US/…
2
3
278
主线程等 Worker 的共享内存信号,不一定要卡住。Atomics.waitAsync() 可以异步等待,但名字里有 Async,不代表每次都直接给你一个 Promise。 先看它等什么:传入的必须是基于 SharedArrayBuffer 的 Int32Array 或 BigInt64Array,再指定下标、预期值和可选的超时毫秒数。普通数组、普通 ArrayBuffer 都不在这套机制里。 返回结果分三种: ① 当前值和预期不一样,立刻得到 { async: false, value: "not-equal" }。 ② 值相等,但超时设为 0,立刻得到 "timed-out",async 仍是 false。 ③ 需要等待时,async 才是 true,value 才是 Promise;收到 Atomics.notify() 通知后兑现为 "ok",等超时则是 "timed-out"。 例如共享数组第 0 项为 0,调用 Atomics.waitAsync(int32, 0, 0, 1000),就是最多等 1 秒。另一个线程对同一位置调用 Atomics.notify(int32, 0),可以把等待者唤醒。这里的 "ok" 表示收到通知,不能直接当成某项业务已经成功。 几个容易漏的边界:它不会阻塞整个线程,因此能用在主线程;但需要同步的后续操作,仍要等结果回来,不能因为界面没卡住就提前读写。返回的等待 Promise 不会 reject,不过参数类型不对、下标越界,调用本身仍会抛异常。 超时不传,或者传入 NaN,会按无限等待处理;负数则按 0 处理。浏览器里还得满足 SharedArrayBuffer 的安全要求,不是把示例随便粘进任意网页控制台就能运行。 这类 API 适合协调共享内存上的线程工作。处理时要分清“无需等待”“等到通知”和“等待超时”,别只看到 Async 就按普通异步函数接。 英文资料:MDN developer.mozilla.org/en-US/…
4
3
151
Gemini Omni 1.2 Flash 把视频生成拆成了一套能反复改的流程:先出低分辨率草稿,改局部,挑满意的再升清晰度。一个细节不对,不必整条推倒重来。 时长从上一代最长 10 秒拉到 15 秒,生成和编辑都支持。可以在 3—15 秒之间按 1 秒递增指定。720p 价格是每秒 0.07 美元,比 Omni 1.1 低 30%;打样可选 360p 或 720p,再把选中的片段升到 1080p、4K。 参考素材也能混着喂:人物、产品、风格图片,最长 3 秒的动作片段,再加音频提示。比如一张模特照配三套衣服的正反面图,生成换装片段;首尾两张关键帧,也能用来衔接镜头。 多轮编辑是在同一条视频上接着改。换产品、换背景、调光线天气、改宽高比,都可以围绕完整的 15 秒片段继续操作。不是每改一次,就重新描述一遍整场戏。 这版还主打人物、物体和场景的一致性,多角色语音与口型,以及穿行、拉远、环绕等连续运镜。这里说的是能力改进,不是保证每条都稳。快速体育动作、剧本急变和复杂物理,仍是待改进的部分。 接入分几路:开发者用 AI Studio 和 Gemini API,企业用 Google Cloud;应用端有 Flow、Google Vids,Gemini App 正向 Google AI Ultra 用户推出。企业侧支持预置吞吐量,生成结果带不可见的 SynthID 水印。
21
1
34
941
Unleash 的功能开关,后端和前端走两套路子。差别在于谁拿规则、谁来算。 后端 SDK 把配置拉到内存里,结合用户上下文,在本地判断开关。业务代码查一次开关,就是跑一次本地函数,不必每次都问服务器或数据库。 浏览器和手机端 SDK 拿的是评估结果。它们提交上下文,由 Unleash 主服务或 Edge 算出哪些功能启用,再把结果缓存下来。注意这个区别:后端接了 Edge,还是后端 SDK 自己算;前端接了 Edge,才由 Edge 算。 数据去哪儿,也得分开看。后端的用户数据留在应用内;前端直接连主服务,就得把上下文交给主服务。前端通过自己部署的 Edge 评估,可以把这部分数据留在自己的边界内。“用户数据不出应用”这句话,不能随便套。 Edge 夹在 SDK 和主服务之间,做缓存、承接大量连接,也负责前端的评估,减少主服务压力。后台用 Admin API 管配置,后端 SDK 用 Client API,前端 SDK 用 Frontend API;后两者也接收 SDK 使用统计。 省掉每次远程查询,也得接受配置传播的延迟。控制台改完,传到应用通常要几秒,延迟可调。拨了开关,不会让所有应用瞬间一起换挡。 开源版可自行部署,代码是 AGPL-3.0-or-later;SSO、RBAC 等属于商业方案,别当成全套免费。
5
1
24
559
P-JiT 这条文生图路线,省算力的重点是少算 token、让中间层早点得到反馈,再分阶段补细节。 1. 不另接一个 VAE 来压图,而是在像素空间一起学压缩和生成。图片切成 32×32 的块,每块 RGB 的 3072 个数投到 1024 维。512×512 图片变成 256 个图像 token,再加 256 个文本 token;文本表示来自冻结的 Qwen3.5-4B。 2. 图像和文本各过两层整理,再合进 22 层 DiT。第 10、16 层分别预测四分之一、二分之一边长的图,最后一层出全图;第 5 层还用 PixelREPA 对齐 DINOv3-L 特征。中间层也要交作业,梯度反馈不用全等到最后。辅助出口只训练时用,采样时移除。 3. 训练先抓结构,再抠细节。前 8000 万次样本处理集中在高噪声,之后放宽噪声分布。先在 512 档分辨率训练约 6.1 亿次样本,再升到 1024 档训练约 5000 万次,两个阶段都混合五种宽高比;升分辨率时重置优化器、承接 EMA 权重,并把噪声尺度加倍。 4. 账单分开算:预训练约 2.03 万美元,后训练约 690 美元,合计约 2.10 万美元,按每 H100 小时 2.5 美元核算。这是算力费用口径,不能直接当成整个项目的总成本。后训练采用 FDFO 的变体,结合模型评审与人工反馈。 5. 能力边界很直白:训练数据排除了文字密集图片,因此这版不能正常写字或生成文字海报;全身人体、多对象、多人互动和常识仍会出错,比如骰子各面都画成六点。代码和权重按 Apache 2.0 开放,但这仍是走向更大视频模型途中的实验版,不是已经解决视频生成。
5
14
362
BUI 把 Blender 的界面能力拆成库,源码里却有两条路线:真正跑应用的 C 运行时,和供参考、继续移植的原始 UI。别一锅端着看。 运行这条线:Python、Rust、JS/React、Dear ImGui 和 C 入口,汇到 kit 的节点树、布局、输入、控件、菜单、撤销和窗口。原始 ui 目录默认不编译;场景、渲染器、操作器等 Blender 应用逻辑已切掉。 绘制分两路:默认是 CPU 软件光栅器,按条带并行画抗锯齿图元。GPU 则是可选的 Windows D3D11、Linux OpenGL 3.3;两边对应同一套覆盖率公式,并有像素一致性测试。GPU 设备丢失时,回退软件渲染。 省活儿靠复用:帧目标保留,只重画变化区域;图片按显示尺寸解码,由工作线程处理,缓存有内存预算。视频每个播放器复用两个 NV12 帧缓冲,GPU 纹理原地更新,在着色器里转 RGB;动画只在屏幕上可见时推进。 边界也写明了:原始 ui 源码能编译链接,不代表已经能原样托管完整控件树,事件、上下文、属性访问等接口仍待接好。现有应用走 kit;Cocoa 窗口后端仍是实验性。 多语言共用运行时,绘制后端和宿主窗口各守边界。这才是这次拆库的结构,不能把“能编译”直接当成“全搬完了”。
1
1
15
303
把浏览器引擎嵌进应用,还要照顾页面隐藏、窗口失焦、出错和恢复时的行为。Servo 11月5日发布的9月开发汇总里,有四组值得拆开的实现细节。 ① 隐藏视图,少做无用功 Servo 这轮移除了手动设置节流的 set_throttled 接口,把节流与 WebView 的隐藏、显示关联起来。隐藏的视图不再接收新帧通知,也不再触发 RefreshDriver 的动画 tick。 宿主负责告诉引擎界面是否可见,引擎据此减少工作。做多标签页或多面板应用时,除了检查切走后动画是否停下,也要检查恢复显示后能否正常更新。 ② 把焦点状态分清楚 宿主通过 set_focused 告知 Servo 系统焦点的得失,并把它与网页内部的焦点状态分开。WebDriver 选中哪个视图进行交互,也要与系统焦点分开处理。 例如两个面板并排可见,一个没拿到键盘焦点,并不等于它已经隐藏。可见性、系统焦点、页面内部焦点分别建模,可以减少状态混用带来的问题。 ③ 错误页也要经得起测试 Servo 曾有一个连锁问题:事件循环崩溃后虽然换上了错误页,旧的页面处理管线却没清理干净。后续消息继续发往已崩溃的管线,又触发错误,还可能被归到新的错误页上。 修复会清理失效管线,让后续发给它们的消息被忽略。新增测试主动触发一次崩溃,检查只收到一次崩溃通知,再调整视图大小、等待渲染,并确认画面不是一整块纯色。恢复路径也有了可回归的检查。 ④ 在控制台看见 Promise 错误 另一项修改把未处理的 Promise 拒绝送进开发者控制台。此前它只出现在 Rust 日志里。新逻辑会先触发 unhandledrejection 事件,事件没有被取消时再输出 console.error。 新增测试用的是 Promise.reject(5),检查日志级别为 error,以及输出中是否包含拒绝原因。开发者排查异步问题时,能直接在控制台看到这类错误。 这些细节对应 Servo 本轮实现。隐藏视图的优化 PR 也留下了明确边界:性能效果难以通过自动测试直接验证,没有给出能通用套用的提速比例。
2
12
226
Firenook 这个 Firebase 本地模拟器,把“写入快40倍”背后的工程取舍讲得很具体。拆开看,有几条适合做开发工具时参考。 先看数字的边界:同一台服务器、同一个应用,分别跑两小时混合负载,写入确认延迟中位数从208.2ms降到5.1ms。但模拟器单独运行时,“写入再删除一条文档”的差距只有3.01ms对1.96ms,约1.5倍。40倍描述的是特定负载下的结果,不能套到每次写入。 ① 把提交做小。 一次写入只读取受影响的文档,算出新值,追加日志,再把文档和索引变化提交给嵌入式数据库redb。提交工作放到阻塞线程,避免占住负责读请求的异步线程。 ② 写成功和断电不丢,是两种承诺。 默认模式在返回写入确认前,先完成日志的普通write,再提交redb。后台约每秒同步一次。进程被杀后,重新打开同一数据存储,可重放日志恢复已确认写入;断电或内核崩溃时,最近一次同步后的写入仍可能丢失。redb的Durability::None本身不保证落盘,需要后续Immediate提交。 注意“同一存储”这个条件:日常续用应从一开始启用--keep-data;自定义状态目录需用--state-dir指定,并以--resume-state续用。普通默认启动会新建空状态,不会自动加载上次会话。 这里可以改用逐次同步,但延迟会增加。不能因为它适合可重建的测试数据,就把同样策略原样搬进支付账本。 ③ 用索引减少读取,用增量减少重算。 例如按createdAt倒序取20条,符合索引条件时,可以从有序索引定位并提前停止,不必先读完整个集合。监听器通常根据变更记录更新视图;提交线程只发出版本变化通知,不等所有监听器处理完。limit窗口里的文档离开或移动时,可能触发重新查询补位。 ④ 接口能接上,还要检查语义。 Firenook保留原来的配置、SDK连接方式和端口,但不覆盖所有Firebase服务。它的事务冲突处理也与官方模拟器有差异。现阶段使用--keep-data,还要求Firestore、Auth和Storage一起运行,不能只看一条启动命令就迁移。 测试本身也有边界:仅一台机器、一个应用,应用和数据未公开,外部无法原样复跑。两个本地模拟器的性能对比,更不能直接推导生产Firestore的性能。 恢复保证还要看平台:网络文件系统上的持久性、Windows断电恢复目前尚未验证。开发时也应隔离测试项目,确认未模拟的服务不会意外连接真实后端、修改数据或产生费用。 做这类评估,至少同时看四件事:确认延迟、持久性承诺、读写放大、兼容性。再用自己的数据量、查询形状和故障恢复测试去验证。一个醒目的倍数,代替不了这些检查。
5
17
506
Inkling 2 Small 值得关注的,是把视觉反馈、长任务和可调推理放进同一套模型里。接入前,有几处细节最好分开看。 1. 参数规模 总参数 594B,每个 token 激活约 17B,采用 MoE 架构,路由从 384 个专家中选 8 个。激活量影响单步计算,部署还要考虑全部专家权重、缓存和运行开销,不能只按 17B 稠密模型估算显存。它接受文字和图片、输出文字,不支持音频。网页和 3D 演示主要靠生成代码、调用工具完成。 2. 长任务训练 联合强化学习把代码、文档、视觉、推理、工具使用等任务放在一起,不同任务使用测试、评分规则或截图比较来判断结果。公布的训练轨迹时长中位数为 8 分钟,p99 为 4 小时;轨迹总 token 数的中位数为 43K,p99 为 1.5M。这些是两组分布统计。轨迹可以跨多轮,训练使用 256K 上下文并压缩较长历史,别把累计消耗当成单次窗口。 3. 模型上限与接口上限 模型卡写的是最高 1M 上下文;目前 Tinker 训练型号列出 64K、256K 两档,Serverless 推理列的是 256K,仍处于 Beta。做长文档检索或仓库分析,应按实际调用的型号留预算。长对话也可能出现能力退化,窗口装得下不代表每处细节都能准确利用。 4. 视觉成绩与接入细节 公布的 ScreenSpot Pro 成绩为 83.8%,使用 Python 工具的版本为 91.5%。评测对长边超过 4096 像素的图片及其标注做了相应缩放,比较时要看清设置。它测的是界面目标定位,不是完成整个 GUI 任务的成功率。 处理票据、截图等任务,输入格式也要对齐:当前 Cookbook 接受 PNG、JPEG,渲染器不会自动下载 HTTP 或云存储图片地址,需要应用先取得图片数据。原生图片指针还要提供正确宽高。 5. 推理预算 effort 可以在 0 到 1 之间连续调节,但不能取 1;默认 high 是 0.9,xhigh 是 0.99。它和 max_tokens 独立。复杂任务提高 effort 后,输出预算太紧仍可能被截断;0 也不保证绝对不推理。选参数最好看多次样本的正确率、延迟和成本。 11 月 5 日的发布公告计划在完成外部安全测试后,于下一周按 Apache 2.0 公布权重,具体开放时间以正式发布为准。对开发者来说,可以先用真实任务测工具选择、长对话稳定性和视觉输入,再用自己的验收标准判断是否值得迁移。
1
14
252
做 Agent 服务,一次构建把内存打满,不该连会话记录、重试逻辑也一起带走。Rivet 这次发布里,有几个架构细节值得拆开看。 ① 每个 Agent 有自己的 Actor Actor 可以按用户、会话或任务分配,带 SQLite、消息队列、WebSocket 和定时调度。多个 Actor 共享节点进程,不是每个都启动一台虚拟机。空闲时休眠,再带着已保存的数据唤醒。 比如多人共用一个代码助手:历史放进这个 Actor 的数据库,生成进度推给连接着的客户端,需要执行代码时再调用沙箱。 ② 按执行需求配沙箱 只调 API、MCP 和业务工具的 Agent,可能不需要代码沙箱;数据处理可选轻量隔离环境;复杂开发任务仍可接完整沙箱。模型调用、权限检查、审计和恢复逻辑留在可信后端,文件与命令工具才进入执行环境。Actor 共享进程,本身不能被当成运行任意不可信代码的安全边界。 ③ “存到 S3”有一层 SSD 缓冲 在官方三可用区配置中,写入先复制到各区,两个区的磁盘确认后返回,再后台批量写进对象存储。热数据从 SSD 读,冷数据才去对象存储取。因此,1.07ms 的 p99 持久化写入,不能读成“每次直接 PUT S3 只要 1ms”。自托管若只跑单实例,磁盘丢失会丢掉尚未进入对象存储的写入。 ④ 基准要看分母 官方列出 4.99ms p99 冷启动、72.4KB 单 Actor 内存。但后者是 Rust SDK 测到的增量 RSS;拿来对比的 E2B 512MiB 是默认分配量,含义不同。 冷启动也分开测:Rivet 一次一个请求,E2B 数字来自另一组 100 个并发启动的测试,环境不同。不能直接拿倍数预测自己的业务。 “存储便宜约 10.4 倍”则是 S3 与三份 EBS gp3 的静态容量标价比较,未计对象请求、跨区复制流量和暂存新写入的本地磁盘,不能当整套服务的账单折扣。 选这类基础设施,先画清状态保存在哪、谁负责唤醒、生成代码在哪执行,再按自己的并发和故障场景测试。
3
9
170
Alpine 上跑 SSH 的,别只升包就收工。这次补丁还得重启 sshd,旧进程不会自己换掉。 坑在密码校验:musl 的 crypt() 在特定失败情况下返回“*”,而没设置密码的账号也可能存着“*”。把错误标记当成正常结果去比较,居然能对上。 不是所有 Alpine 都中招,得同时满足: • sshd 未启用 PAM:使用 openssh-server,或 openssh-server-pam 配置了 UsePAM no; • 允许密码登录; • 目标账号密码字段是“*”; • 机器上另有本地账号用了 $5$ 或 $6$ 密码哈希。 所以“我一直用密钥登录”不等于服务端关了密码认证。没跑 sshd 的容器不受这个问题影响;root 默认禁密码登录,也不能替其他账号兜底。 修复别只盯上游 10.6p2。Alpine 已给旧分支回补,3.21.10、3.22.8、3.23.8、3.24.4 都含修复包。升级后重启服务;暂时升不了,就将 PasswordAuthentication 设为 no 并重启 sshd。操作前先确认密钥能登录,留着旧会话,另开一个验证,别把自己锁门外。
7
129
N64 放背景音乐,卡带装得下还不够,解码也不能抢光游戏的算力。ULC 这种低复杂度音频编码,把不少取舍直接放进了格式设计。 1. 少让解码器干活 ULC 基于 MDCT,把音频变成频域系数。编码时先完成归一化,解码端做逆变换时就能省掉对应的缩放。 数据按 4 bit,也就是半个字节组织,不用做 Huffman 熵编码查表。普通系数、连续零、噪声填充、量化参数调整,都有各自的标记。 这里的 4 bit 是编码语法的单位,别读成“每个 PCM 采样只剩 4 bit”。 2. 省下来的数据,有听感代价 连续的零可以合并记录;部分没直接保存的系数,用噪声填充。窗口切换和重叠范围调整,用来控制瞬态声音的泄漏。 但声音突然衰减时,填进去的噪声可能漏出来,左右声道都可能受影响。心理声学模型也比较简化,没有充分利用跨块的时间掩蔽。 3. 块大小得给硬件让路 N64 的 RSP 只有 4KB 数据内存、4KB 指令内存,向量运算用定点数。默认 2048 采样块,直接塞不下。 留出工作空间后,有两种同样占 3072 字节的组合: - 32 位:512 采样块 + 256 采样重叠缓冲。 - 16 位:1024 采样块 + 512 采样重叠缓冲。 这套实现选了后者,保留较大的块,也减少 DMA 搬运量,代价是噪声底可能升高。 把逆变换等工作交给 RSP 后,单条 44.1kHz 双声道音轨约占 CPU 4%、RSP 5%。这是两个处理单元的占用,不能加成“9% CPU”。放进游戏,还得看它与图形任务争用的情况。 4. 目标码率和容错,别漏看 核心机制是 VBR。工具提供的 CBR、ABR,是靠调整质量逼近目标码率,会拖慢编码;不能理解成每个块都严格等长。 格式没有块同步标记。文件损坏后,不能指望扫描一下就稳定找到下一块。编码语法灵活,解码器也得认真检查边界。 选编码器,文件大小、解码耗时、内存占用得一起算。压得再小,解码一跑游戏就掉帧,也白搭。
4
2
247
AI 找洞提速了,补洞却没法跟着按快进。PHP 最近的安全更新,就把这两种工作量摆在了一起。 两项漏洞,根因不同: ① 内存耗尽后,部分内部数据只初始化了一半,后续却被当成完整数据使用。 ② 错误处理留下旧指针,后续请求又访问了已经释放的内存。 两者都可能导致远程代码执行,具体路径取决于部署和应用行为。不是一句“内存不够,扩容就行”能解决的。 修复过程里,有四个容易漏看的点。 1. 修法要过兼容性这一关 2024 年已有内存耗尽问题的迹象,2026 年 8 月才证实完整的远程执行路径。修复方案之间还要权衡兼容性、拒绝服务风险和第三方扩展改造,不能只看补丁改了几行。 2. 修补要覆盖相关路径 发现者只需证明某条路径能打通;修复者得检查相关路径,还要确认旧功能、性能和扩展没有被带崩。复现、测试、审查、发布,都是补丁的一部分。 3. 报告数量也会变成维护成本 PHP 团队从 7 月 1 日起公布了 15 个 CVE,也处理了超过 100 份无效、重复或无法复现的报告。AI 能辅助验证和归组,判断和补丁仍有人负责。 4. 证明能帮忙,但证明也有边界 让模型写补丁、生成证明,再交给独立检查器验算,是另一条路线。检查器验证的是证明与规范是否相符,不会自动保证规范完整对应现实。覆盖哪些性质、采用什么 CPU 和系统模型,都得说清楚,不能直接变成“整个软件没漏洞”。 10 月 21 日的 PHP 8.5.12、8.4.27、8.3.36、8.2.35 已修复上述两项问题。应用代码没改,也不代表底层运行时不用更新。 找洞快了,后面的验证和修复更不能省。
4
1
3
303
Images 2.6 这次有两个旋钮,别拧混了:quality 管画面的细节和保真度,complexity 管模型为场景和约束花多少规划功夫。 “画得细”和“想得多”不是同一件事。把新能力拆开看,比只盯着出图速度有用。 1. Flare 和 Sunburst,侧重点不同 API 提供 GPT-Image-2.6 Flare、GPT-Image-2.6 Sunburst。Flare 偏快,low 画质下最快可以约 6 秒出图,也改善了海报、商品图的排版。Sunburst 在 medium、high 画质下偏重细节、精度,以及连续修改时的一致性。 这次发布给出的整体速度提升是 50%。但“最快 6 秒”有模型和画质前提,不是所有请求都能卡着秒表兑现。 2. quality 和 complexity 可以分开选 只是改个局部,不一定需要高复杂度。可以用 low complexity 加快编辑,同时保留工作所需的画质。 场景里元素多、约束多,再考虑 medium 或 high complexity。降低规划力度,不等于必须把画质一起降下来;提高画质,也不等于模型就会更认真地安排场景。 3. 不只出静态图,还能做 GIF 用途包括品牌吉祥物、表情包、商品循环动画,以及正式拍视频前探索动作的动态分镜。简单草图也能变成动画角色。 一个提示词示例把办公室小人的反应拆成 6 帧,按 3 列、2 行排好,固定角色、镜头、服装和背景,只让表情、动作逐步变化,并要求最后一帧能接回第一帧。这种约束重点在于“连续且能循环”,不是六张各画各的图。 另一个例子是把动画角色放在工作助手的输入框上方,充当正在输入的提示。 4. 标注编辑和 SVG 进入 API 工作流 图片标注可以明确指出“改这里”,减少只靠文字描述位置的绕弯。SVG 输出则让结果可以继续用于网站、应用和印刷。 连续编辑的重点是改动目标部分,同时保留人物、产品和品牌细节。客厅示例把靠垫改蓝、移除盆栽和地毯,再把落地灯搬到右侧;骑士示例则先换背景,再改铠甲与布料颜色,保留人物姿态和构图。 5. 动起来之后,审核也更难 GIF 的风险不一定藏在某一张静帧里,动作和前后帧组合也可能改变含义。因此它增加了专门的审核规则,有些类别的拦截范围比静态图更广。 在一组刻意构造、已标为违规的 GIF 测试里,审核召回率为 77.7%。这个数字说明仍有漏检,不能换算成日常用户生成内容的违规概率,也不能理解成“所有 GIF 有 22.3% 不安全”。 6. 来源标记不是只靠一层 保留 C2PA 元数据,并加入 SynthID 不可见水印,覆盖 ChatGPT、Codex 和 API。两者互补,但没有哪一层能单独包办来源验证。 Images 2.6 已在 ChatGPT 和 Codex 的各订阅档位上线,覆盖桌面、手机和网页;API 提供上面两个模型。
5
1
3
360
Zig 开始拒绝一部分“合法但容易看错”的表达式:不是换一张更长的优先级表,而是让某些运算符组合必须写括号。 这项改动已进入主分支。它采用部分运算符优先级:常见组合仍有明确顺序,容易混淆的组合不再替你选一种解释。旧的 0.17.x 优先级规则不能直接套过来。 可以分成几类看。 1. 普通算术保留熟悉的规则 a + b * c 仍然先乘后加。算术运算高于比较,比较高于逻辑运算,不需要给每一步都套括号。 但 % 是个例外:它仍高于加减,和乘除混用、甚至连续使用自身,都需要括号。a % b % c 不能再靠默认顺序读过去。 2. 位运算不能随便混着写 不同位运算符混用要明确分组。&、|、^ 可以各自连续使用,例如 a ^ b ^ c;移位则连自身串起来也不行,a << b << c 需要括号。 比较运算比算术和位运算优先级低,但比较本身不能连成一串。 3. and 和 or 混用也要括号 连续几个 and,或者连续几个 or,可以照常写。a or b and c 则需要明确选择 a or (b and c) 还是 (a or b) and c,不能再凭其他语言的习惯阅读。 4. 空值与错误恢复特别值得检查 orelse 可以和自己串,catch 也可以和自己串,二者混用需要括号。前缀 try 的优先级仍高于它们。 x orelse y == z 现在会报歧义错误。旧规则按 (x orelse y) == z 求值,而 x orelse (y == z) 可能得到不同结果。 例如 x 是可选布尔值 true,y 和 z 都是 false:前一种结果是 false,后一种会直接得到 true。括号不是装饰,它可能决定程序做了什么。 5. 自动升级保存的是旧行为,不是你的本意 可以逐个修编译错误,重新判断本来想要哪种分组;也可以运行 zig fmt --upgrade,自动补括号,保持旧优先级的求值顺序。 --upgrade 是临时迁移参数,计划一个发布周期后移除。自动处理后仍要看 diff,也可能需要清理冗余括号。 典型例子是 a orelse b() catch c:自动迁移保留 (a orelse b()) catch c,但业务上可能更想写 a orelse (b() catch c)。即使某些场景最终结果没变,也值得确认意图。 这次 Zig 自身一百多万行代码里,有 353 行需要调整。它不是要让所有表达式都变得很啰嗦,而是把少见、容易记错的优先级组合交给编译器提醒。 看见明确分组,比让每个读代码的人重新背一遍规则更可靠。迁移时最值得检查的,也不是“括号补齐了吗”,而是“补出来的顺序真是原来想要的吗”。
3
1
5
515
网页上那种“用手指把一团液体抹开”的效果,不一定需要让 3D 网格真的鼓起来。把运动、厚度、残留颜色分开处理,平面也能画出有高光、有堆积感的液体。 用 three.js 做这类交互,可以按下面几层拆。 1. 速度图管运动,高度图管厚度 先从流体模拟得到每个位置的速度,再用一张高度图保存液体有多厚。初始形状可以是中间高、边缘低的半球状小团,厚度放在纹理的 R 通道里。 更新时,每个像素先按自己的速度减去一部分厚度,再沿速度反方向回溯,找到上一帧的来源位置,把那里的一部分厚度加进来。结果不能小于零。 用 smoothstep 给低速区设一个平滑过渡,让静止位置基本不动,主要响应拖拽附近的流动。每帧从旧纹理读、向新纹理写,两张 FBO 交替使用。 2. “抹过的痕迹”需要单独记忆 只看当前厚度,液体移走后,原来的颜色也会消失。可以再加一张 alpha 图,记录液体曾经经过哪里。 把当前厚度转换成一个覆盖值,与历史最大值比较。R 通道保存新的历史最大值,G 通道保存“当前值与历史最大值各占一半”的混合结果。最终渲染读取 G 通道。 这样即使液体被抹薄,表面仍会留下颜色。这只是保留涂抹历史,不是模拟干燥或吸收。 3. 立体感来自法线,不是网格变形 读取当前像素和 X、Y 方向相邻像素的高度,构造两条表面向量,叉乘后得到法线。再把法线映射到 Matcap 纹理上,取出颜色和高光,配合 alpha 图显示。 高度图决定起伏,Matcap 决定表面外观,alpha 图决定哪里还有残留。网格顶点不用跟着移动。 4. 这套厚度更新不保证体积守恒 片元着色器里的目标像素各自回溯采样,多个像素可能取到同一片来源区域。减去的总厚度与加回的总厚度不一定相等。 直线拖动时看起来可能比较稳定,持续画圈却可能越抹越多。适合以观感为目标的交互展示,不能直接当成物理准确的液体模拟。 5. 纹理精度会影响高光 8 位纹理每通道只有 256 级,厚度被量化后,法线和明暗也会出现阶梯。32 位浮点纹理能保留更细的高度变化,轮廓与阴影更平滑。 6. 多种颜色可以共享运动,分别保存历史 三团液体可以共用一张速度图,但各自拥有高度图与 alpha 历史。按各自厚度,用简化的 Kubelka–Munk 模型混色;法线从总厚度计算。厚度接近零的位置,再用各颜色留下的 alpha 历史决定混合比例。 这类效果的实用之处,是把“怎么流”“看起来多厚”“抹过后留下什么”拆成独立的数据。视觉上要什么,计算上就能分别调整,也更容易指出哪些地方只是近似。
5
4
224