MoreRSS

site iconMoreality修改

偏个人软、硬件开箱记录的博客
请复制 RSS 到你的阅读器,或快速订阅到 :

Inoreader Feedly Follow Feedbin Local Reader

Moreality的 RSS 预览

旅行日记·大连

2025-06-10 08:50:32

🖼️ 多图

day 0 - 启程

坐上 K684,开启去往大连的旅途。

这趟卧铺有很多好处:

  • 北京-大连分别是始发和终点站,床上用品都换过了。
  • 可以在第二天一早抵达大连。
  • 工作日人超少(这节车厢中上铺都没有人,下铺也没满),一晚上都很安静。
  • 大连站刚好在市中心

day 1 - 东港

酒店定在了中山广场,

早上 7 点到了酒店,直接就可以办入住了(赞一波亚朵)。

然后包放下以后就直接坐地铁来到了东港。

这一天还是端午节的最后一天,并且天气一般,预想到体验可能不会很好,不过实际逛下来还是比想象中的好不少。

几个景点的感受:

  • 东港广场: 喷泉附近有很多的海鸥,用北京带来的面包(本来准备火车上当早餐的)喂了一波,感觉还挺有意思的,不过小心 🐦💩。
  • 威尼斯水城: 白天没什么好逛的,就是一个小商业街的模式,听说晚上的灯光氛围还可以,不过后面也没有晚上来过。

回酒店吃了一个午饭歇了一会,下午直接打车到海之韵公园继续漫步。

海之韵公园从北门进,上来就是一段超级大上坡,差点没给人累死,在大太阳下走了快半小时才到山顶,不过从山上眺望大海的感觉还蛮好的。

往山下走,为了看一个网红景点「圣象天门」跟着一群大胆的游客翻了好几个护栏和挡板,到了以后发现不过如此,一大群人排队围着一个山洞拍照,虽然到达这里花了很大一番功夫,但是真正到了之后,我也懒得挤过去凑热闹了。

可能每个人对于旅行的理解都不一样,不过我是不太喜欢到了一个地方,就要打卡那些「出片」的网红景点,反倒是喜欢钻入无人的犄角旮旯去探索一下。

到达这里需要翻好几个封锁,到了发现一群人挤着一个小山洞拍网红打卡点

一路上发现了好多可爱小猫,最喜欢的是这只大橘和这俩狸花:

大橘趴在亭子的角落,一动不动望着大海好久

这两只狸花好像是兄弟,一直形影不离

出了海之韵公园南门,就一直沿着滨海东路走啊走啊,一个下午走了快 30km,4 万步。自己走路有一个好处就是不必迁就他人,遇到想走的路和想看的东西,就直接走就完事了。

大连是丘陵地带,海边没有沙滩,只有断崖和礁石滩,这边整个滨海东路都是断崖,也是一种独特的风格。

随处可见这样的告示:

途中遇到可以拐下去的地方,无一例外都选择了下去看看,遇到了很多惊喜的景点。

许多地方就是沿着断崖修建的小路或者房子,左边是树林,右边就是一望无际的大海。

day 2 - 亲海栈道

第二天一早就打车来到了老虎滩-菱角湾。

假期过了,天气晴了,大海的颜色变成深邃的蓝,拍照非常好看。

大连的海边有非常多的本地钓鱼佬,都是轻装出行,一根钓竿一把小椅子,一坐就是大半天,给人一种非常 chill 的感觉。

一路上都是这种非常舒适的步行木栈道,基本覆盖了从海之韵公园出来一直到星海广场这一条几十公里长长的海岸线。

走到付家庄公园,远看是一个沙滩,近看原来是石子组成的。

一对新婚夫妻正在拍婚纱照,花拱门背对着就是一个带娃的母亲

快黄昏时走到了大连的标志性建筑「星海湾大桥」,这个 6.8km 的跨海大桥从各个角度看过去都很壮观。

日落时桥上还会亮起不同颜色的灯光,不过当时有点冷,就没在山顶逗留了,不然俯视角下的夜景应该会很好看。

day 3 - 一些城市景观

体验了一下大连的有轨电车,由于是工作日车上没什么人,车晃悠的不行、叮铃哐啷的很有意思。

大连城市的街道两旁见不到任何共享单车,不过倒是有一些共享的电动车,可能是因为城市规划的原因(没有非机动车道),并且丘陵地带很多陡峭的上下坡,确实不太适合骑单车。

晚上 8 点来到南山街,没有想象中热闹的街景,只有节日过后人们正在收拾残局。

而且大连的风真的很大,即使在城区里也能吹的人头秃,于是匆匆离开。

后记

写这篇 Blog 的时候这次旅行已经过去快 2 周了,不过一切景物还是历历在目。

旅行这种事情确实能极大的减缓时间流逝的感觉,让人重拾起一种控制时间与生活的信心。

滨海城市的旅行体验真的极好,有时间会再去威海、青岛这些地方看看。

很喜欢的一句话:

只有一个人旅行时,才听得到自己的声音。它会告诉你,这世界比自己想象中更宽阔。

天津天津

2025-05-29 19:35:05

毕业答辩结束,又因为还需要等着提交材料没法长途跋涉,想着天津离北京这么近又还没去过,索性去看看。

初印象:混乱街道与大碗饭菜

5月末,白天的天津,炎热难耐,走在大街上,似乎不存在斑马线和行人红绿灯。

好多好多的单行道和狭窄街道。下火车出了地铁左转,因为没有人行道,只能在逆行穿过一股酒味的涵洞。

第一顿当然尝一下天津菜,没想到分量巨大无比,一盆炒鸡估计炒了不止一只鸡。

两个饭量还不错的人吃了一个小时,最后菜和没动一样。

海河游船

晚上去天津站码头坐了海河游船,也是此次天津之行感觉最值回票价的体验。

出了天津站就是码头,长长的海河沿线几乎包含了所有天津有名的景点。

如果天津没有这条海河,也许我给的评价还不如我老家。

周围高楼林立,每个楼都闪着金光,有种轻奢感。

平平无奇的周内工作日,海河周围散步的人也是熙熙攘攘。

到了晚上 10 点多才逐渐安静下来,这时沿着河边散步几乎只剩下一个个卖酒的小摊贩。

心情还不错的时候,吹着晚风这么喝一杯还挺惬意。

白天的天津:炎热而无聊

白天走马观花的看了世纪钟广场、津湾广场、西开教堂、北安桥、意式风情街、瓷房子、五大道等等一系列建筑。

如果天气凉快一点,也许还有心情停下来欣赏一番,然而 30 度的艳阳天只能让我像特种兵打卡一样匆匆拍个照就走人。

体验一般,不过即使是凉快下来,感觉白天的天津相比晚上也差了不少意思。

相声:有点意思

下午的时候实在逛不下去了,干脆花 2 小时去听下相声。

直到开场的时候才陆续来了其他两桌人,不然直接包场了,那样还挺尬的。

第一次线下花这么长时间完整的听完一场相声,感觉还不错,主要空调还很凉快。

最后又在夜风中搭观光车溜达了一圈,感觉天津城区真的好小,已经有点腻了。

还是更喜欢山川湖海。

Google「提示词工程」白皮书阅读总结

2025-04-21 22:45:28

前言

注意,本文不是翻译,只是针对自己觉得有用的一些地方的记录和总结;对于我自己觉得已经比较清楚的概念,就完全没有记录。其中最后的「最佳实践 Best Practice」部分个人感觉比较有用,强烈建议结合原版 PDF 的一些示例 demo 进行对比查看。

简单介绍:

「Prompt Engineering」是 Google 推出的提示词工程白皮书,一共 60 多页,原始版本可以在 https://www.kaggle.com/whitepaper-prompt-engineering 上获取到,或者直接 下载链接 (googledrive)

封面

1 - 采样控制

主要三个可配置项:

  • Top-P
  • Top-K
  • Temperature (温度)

作用的先后顺序: Top-K -> Top-P -> Temperature

例子

假设我们有以下设置:

  • top-k = 5
  • top-p = 0.9
  • temperature = 0.7

当模型预测下一个词时,可能给出了这样的概率分布:

  • “的”: 0.3
  • “是”: 0.25
  • “在”: 0.2
  • “了”: 0.1
  • “有”: 0.05
  • “和”: 0.03
  • “人”: 0.02
  • 其他词: 各小于0.01

应用步骤:

  1. top-k=5筛选后,只保留前5个词:”的”、”是”、”在”、”了”、”有”
  2. 计算这5个词的累积概率:0.3+0.25+0.2+0.1+0.05=0.9,正好等于top-p的阈值0.9,所以这5个词都被保留
  3. 应用temperature=0.7,通常来说,温度与模型的“创造力”有关。但事实并非如此。温度只是调整单词的概率分布。其最终的宏观效果是,在较低的温度下,我们的模型更具确定性,而在较高的温度下,则不那么确定。(ref: 知乎: 大模型文本生成——解码策略(Top-k & Top-p & Temperature))

极端情况

几种极端情况的 LLM 实现考虑:

  • 温度设为 0 时,Top-K 和 Top-P 无关,概率最高的令牌被选为下一个预测令牌;温度极高(如 10 量级)时,温度无关,Top-K 和/或 Top-P 标准的令牌将被随机采样。
  • Top-K 设为 1 时,温度和 Top-P 无关,仅概率最高的令牌被选;Top-K 设为词汇表大小时,所有非零概率令牌均满足标准,无令牌被筛选。
  • Top-P 设为 0 或极小时,仅概率最高的令牌满足标准,温度和 Top-K 无关;Top-P 设为 1 时,所有非零概率令牌均满足标准,无令牌被筛选。

推荐配置

Use Case Temperature Top-P Top-K Description
通用场景 General Purpose 0.2 0.95 30 Relatively coherent results with some creativity
高创造力场景 High Creativity 0.9 0.99 40 Especially creative results
比较精确的场景 (比如coding)
Low Creativity
0.1 0.9 20 Less creative results
Single Correct Answer 0 - - For tasks with only one correct answer (e.g., math problems)

2 - Guide Prompt 分类

系统提示(System Prompting)、上下文提示(Contextual Prompting)和角色提示(Role Prompting)都是用于指导大型语言模型(LLMs)生成文本的技术,但它们关注的重点不同。尽管这些提示类型之间存在相当大的重叠(例如,一个提示可能同时为系统分配角色并提供上下文),但每种提示的主要目的略有不同。

以下是三种提示类型的核心区别和作用:

提示类型 主要目的与功能 特点与作用
System Prompt 定义模型的基本能力和总体目的。 设定语言模型的“大局观”,例如翻译语言、分类评论等,指导模型的整体行为。
Contextual Prompt 提供与当前对话或任务相关的具体细节或背景信息,指导模型生成针对性的回应。 高度特定于当前任务或输入,具有动态性,帮助模型理解细微差别并调整回应。
Role Prompt 为模型分配特定的角色或身份,帮助模型生成与该角色一致的回应。 塑造模型的输出风格和语气,增加具体性和个性,使回应符合角色的知识和行为。

system prompt

1
2
3
4
5
6
Classify movie reviews as positive, neutral or negative. Only
return the label in uppercase.
Review: "Her" is a disturbing study revealing the direction
humanity is headed if AI is allowed to keep evolving,
unchecked. It's so disturbing I couldn't watch it.
Sentiment:

contextual prompt

1
2
3
4
Context: You are writing for a blog about retro 80's arcade
video games.
Suggest 3 topics to write an article about with a few lines of
description of what this article should contain.

role prompt

1
2
3
4
5
6
7
I want you to act as a travel guide. I will write to you
about my location and you will suggest 3 places to visit near
me. In some cases, I will also give you the type of places I
will visit.
My suggestion: "I am in Amsterdam and I want to visit
only museums."
Travel Suggestions:

3 - 示例个数: one-shot & few-shot

在为AI模型创建提示时,提供示例非常有帮助。示例可以帮助模型理解您的需求,尤其是在希望模型输出特定结构或模式时特别有用。

  • One-shot 提示提供单个示例,因此得名one-shot,目的是让模型有一个可以模仿的示例来完成任务。
  • Few-shot 提示则提供多个示例,向模型展示需要遵循的模式,增加模型遵循该模式的机会。Few-shot提示所需的示例数量取决于任务复杂性、示例质量以及所使用的generative AI (gen AI)模型的能力。一般建议至少使用三到五个示例,但对于复杂任务可能需要更多示例,或者由于模型输入长度限制可能需要更少。选择示例时,应确保示例与任务相关,具有多样性、高质量且编写良好,因为一个小错误就可能导致模型混淆并输出不符合预期的结果。此外,如果希望生成的输出能适应各种输入,包含edge cases(异常或意外输入)在示例中非常重要。

4 - Step-back Prompting (回退提示词)

Step-back prompting 是一种通过提示 LLM 先考虑与具体任务相关的一般性问题,然后将该问题的答案输入到后续的具体任务提示中来提升性能的技术。这种“step back”方法使 LLM 在尝试解决具体问题之前激活相关的背景知识和推理过程。通过考虑更广泛和基本的原则,LLM 能够生成更准确和有洞察力的回应。

Step-back prompting 鼓励 LLM 批判性思考并以新的和创造性的方式应用知识,通过利用 LLM 参数中的更多知识来改变执行任务的最终提示,而这些知识在直接提示 LLM 时可能不会被激活。它还可以通过关注一般原则而非具体细节来帮助减轻 LLM 回应中的偏见。

传统 prompt & step-back prompt 对比

传统 prompt:

1
Write a one paragraph storyline for a new level of a firstperson shooter video game that is challenging and engaging.

step-back prompt:

  1. 首先询问:
1
Write a one paragraph storyline for a new level of a firstperson shooter video game that is challenging and engaging.
  1. 之后基于 LLM 的回答,追加提问:
1
2
3
Based on popular first-person shooter action games, what are
5 fictional key settings that contribute to a challenging and
engaging level storyline in a first-person shooter video game?

5 - APE: Automatic Prompt Engineering (自动生成提示词)

编写提示(prompt)可能是一项复杂的任务。为了解决这个问题,可以采用Automatic Prompt Engineering (APE)方法。这种方法不仅减少了人工输入的需求,还能提升模型在各种任务中的表现。你可以让模型生成更多提示,对它们进行评估,可能的话修改好的提示,然后重复这个过程。

case:

1
2
3
4
We have a band merchandise t-shirt webshop, and to train a
chatbot we need various ways to order: "One Metallica t-shirt
size S". Generate 10 variants, with the same semantics but keep
the same meaning.

6 - Code Prompting

这节没什么特别出彩的,就是列举了几个 coding 的用法,不过可以参考一下 Gemini 在 coding 时是如何推荐配置 topk, topp 和 temperature 的

  • Temperature:0.1
  • Token Limit:1024
  • Top-K:N/A
  • Top-P:1

  • writing code

1
2
3
Write a code snippet in Bash, which asks for a folder name.
Then it takes the contents of the folder and renames all the
files inside by prepending the name draft to the file name.
  • explain code
1
Explain to me the below Bash code:
  • translating code
1
Translate the below Bash code to a Python snippet.
  • debug code
1
2
3
4
5
6
7
8
The below Python code gives an error:
Traceback (most recent call last):
File "/
Users/leeboonstra/Documents/test_folder/rename_files.py", line
7, in <module>
text = toUpperCase(prefix)
NameError: name 'toUpperCase' is not defined
Debug what's wrong and explain how I can improve the code.

7 - 最佳实践 Best Practices

这个很有价值,一些很容易想到和验证的概念,但是 Google 帮你总结出来了

7.1 - 提供示例 (Provide examples)

最重要的原则是在提示中提供示例(one shot/few shot)。这是高效的方法,因为它能作为强大的教学工具。这些示例展示了期望的输出或类似的响应,让模型从中学习并相应地调整其生成内容,提高准确性、风格和语调。

7.2 - 设计简洁明了的提示 (Design with Simplicity)

提示应该简洁、清晰,易于你和模型理解。如果对你来说已经令人困惑,对模型来说也可能令人困惑。尽量不要使用复杂的语言,不要提供不必要的信息。

示例改进:

1
2
3
4
5
改进前:
I am visiting New York right now, and I'd like to hear more about great locations. I am with two 3 year old kids. Where should we go during our vacation?

改进后:
Act as a travel guide for tourists. Describe great places to visit in New York Manhattan with a 3 year old.

尝试使用描述动作的动词,例如: Act, Analyze, Categorize, Classify, Contrast, Compare, Create, Describe, Define, Evaluate, Extract, Find, Generate, Identify, List, Measure, Organize, Parse, Pick, Predict, Provide, Rank, Recommend, Return, Retrieve, Rewrite, Select, Show, Sort, Summarize, Translate, Write.

7.3 - 明确指定输出要求 (Be Specific About Output)

要明确期望的输出内容。简洁的指令可能不足以引导LLM或过于笼统。在提示中提供具体细节(通过系统或上下文提示)可以帮助模型专注于相关内容,提高整体准确性。

推荐做法:

1
Generate a 3 paragraph blog post about the top 5 video game consoles. The blog post should be informative and engaging, and it should be written in a conversational style.

不推荐的做法:

1
Generate a blog post about video game consoles.

7.4 - 使用指令而非约束 (Use Instructions over Constraints)

  • 指令提供了关于期望格式、风格或内容的明确指导,指导模型应该做什么或生成什么
  • 约束是对响应的一系列限制或边界,限制模型不应该做什么或避免什么

研究表明,在提示中专注于积极指令比过度依赖约束更有效。这与人类偏好积极指令而非列出不应做的事项的方式一致。

指令直接传达期望的结果,而约束可能让模型猜测什么是允许的。指令在定义的边界内提供灵活性并鼓励创造力,而约束可能限制模型的潜力。此外,约束列表可能相互冲突。

约束在某些情况下仍然有价值,例如防止模型生成有害或有偏见的内容,或当需要严格的输出格式或风格时。

如果可能,使用积极指令:不要告诉模型不要做什么,而是告诉它应该做什么。这可以避免混淆并提高输出的准确性。

示例:

1
2
3
4
5
DO:
Generate a 1 paragraph blog post about the top 5 video game consoles. Only discuss the console, the company who made it, the year, and total sales.

DO NOT:
Generate a 1 paragraph blog post about the top 5 video game consoles. Do not list video game names.

7.5 - 控制最大token长度 (Control Max Token Length)

要控制生成的LLM响应的长度,你可以在配置中设置最大token限制,或在提示中明确要求特定长度。例如:

1
"Explain quantum physics in a tweet length message."

7.6 - 在 Prompt 中使用变量 (Use Variables in Prompts)

在提示中使用变量,可以针对不同输入而改变。例如,提供关于城市的事实的提示。与其在提示中硬编码城市名称,不如使用变量。变量可以通过避免重复自己来节省时间和精力。如果需要在多个提示中使用相同的信息,可以将其存储在变量中,然后在每个提示中引用该变量。在将提示集成到自己的应用程序中时,这非常有意义。

示例:

1
2
3
4
5
PROMPT: You are a travel guide. Tell me a fact about the city: {city}

VARIABLES: {city} = "Amsterdam"

OUTPUT: Amsterdam is a beautiful city full of canals, bridges, and narrow streets. It's a great place to visit for its rich history, culture, and nightlife.

7.7 - 实验不同的输入格式和写作风格 (Experiment with Input Formats)

不同的模型、模型配置、提示格式、词汇选择和提交方式可能产生不同的结果。因此,实验提示属性如风格、词汇选择和提示类型(零样本、少样本、系统提示)很重要。

例如,对于一个目标是 “生成关于革命性视频游戏机Sega Dreamcast的文本” 的 Prompt,其可以表述为问题、陈述或指令,产生不同的输出:

  • 问题:Sega Dreamcast是什么,为什么它是如此革命性的游戏机?
  • 陈述:Sega Dreamcast是世嘉于1999年发布的第六代视频游戏机。它…
  • 指令:写一段描述Sega Dreamcast游戏机并解释为何它如此革命性的单段文字。

7.8 - 分类任务中混合类别 (Mix Classes in Few-shot)

一般来说,few-shot (少样本)示例的顺序不太重要。然而,在进行分类任务时,确保在few-shot示例中混合可能的响应类别。这是因为否则你可能会过度拟合到示例的特定顺序。通过混合可能的响应类别,你可以确保模型正在学习识别每个类别的关键特征,而不是简单地记住示例的顺序。这将导致在未见数据上更强健和可泛化的性能。

一个好的经验法则是从6个few-shot 示例开始,并从那里开始测试准确性。

7.9 - 适应模型更新 (Adapt to Model Updates)

跟踪模型架构变化、添加的数据和功能很重要。尝试更新的模型版本,并调整提示以更好地利用新的模型特性。

7.10 - 尝试不同的输出格式 (Experiment with Output Formats)

例如,对于非创造性任务,如提取、选择、解析、排序、排名或分类数据,尝试让输出以结构化格式如JSON或XML返回。

从提取数据的提示中返回JSON对象有一些好处。在真实应用中,我不需要手动创建JSON格式,可以直接以排序顺序返回数据(处理日期时间对象时非常方便),但最重要的是,通过提示要求JSON格式,它迫使模型创建结构并限制产生幻觉。

使用JSON输出的好处总结:

  • 始终以相同风格返回
  • 专注于你想要接收的数据
  • 幻觉机会减少
  • 使其关系感知
  • 获得数据类型
  • 可以排序

7.11 - 关于思维链(CoT)的最佳实践 (Chain of Thought Best Practices)

对于CoT Prompt,将答案放在推理之后是必需的,因为推理的生成会改变模型在预测最终答案时获得的tokens。

使用CoT和自我一致性时,你需要能够从提示中提取最终答案,与推理分开。

对于CoT Prompt,将temperature设置为0。

思维链提示基于贪婪解码,根据语言模型分配的最高概率预测序列中的下一个词。一般来说,使用推理来得出最终答案时,可能只有一个正确答案。因此,temperature始终应设置为0。

7.12 - 尝试与记录 (Document Prompt Attempts)

前面已经提到过,但我们再次强调:详细记录你的提示尝试,这样你可以随时了解哪些方法行之有效,哪些不行。

提示输出可能在不同模型、不同采样设置,甚至同一模型的不同版本之间有所不同。此外,即使对同一模型的相同提示,输出句子格式和词汇选择也可能存在细微差异。(例如,如前所述,如果两个tokens具有相同的预测概率,则可能随机打破平局。这可能影响后续预测的tokens。)

建议创建一个Google表格,使用模板记录:

  • 提示名称和版本
  • 目标(一句话解释这次尝试的目标)
  • 使用的模型名称和版本
  • Temperature值(0-1之间)
  • Token限制
  • Top-K和Top-P值
  • 完整提示
  • 输出(或多个输出)

还建议跟踪提示的版本(迭代),记录结果是否OK/NOT OK/SOMETIMES OK,以及反馈。如果有幸使用Vertex AI Studio,保存提示(使用与文档中相同的名称和版本),并在表格中跟踪保存的提示超链接。这样,你只需一次点击就可以重新运行提示。

在使用检索增强生成系统时,还应捕获影响插入到提示中的内容的RAG系统的特定方面,包括查询、块设置、块输出和其他信息。

一旦你觉得提示接近完美,将其带入项目代码库。在代码库中,将提示保存在与代码分开的文件中,这样更容易维护。最后,理想情况下,你的提示是一个可操作系统的一部分,作为提示工程师,你应该依靠自动化测试和评估程序来了解你的提示如何针对任务泛化。

提示工程是一个迭代过程。制作并测试不同的提示,分析并记录结果。根据模型的表现改进你的提示。继续实验直到达到期望的输出。当更改模型或模型配置时,回去继续实验先前使用的提示。

vllm 的 cpu offload 参数卸载逻辑代码简析

2025-03-02 17:08:43

最近在看 vllm 的代码,cpu offloading 这部分它的实现还是比较简单的,这里简单记录一下。

由于大模型的参数量实在很大,所以如果想在单机上运行一般都需要跑量化蒸馏后的模型,但是有时又不想牺牲模型质量,于是CPU/SSD 卸载成为一种折衷方案,通过增加推理时间来降低内存需求。

vllm 也实现了一个简单的 cpu offload 的机制,可以通过 --cpu-offload-gb 启用。

这里发现他的实现还是比较简单的,主要就是通过这个 PR ,添加了一个 func 叫 maybe_offload_to_gpu:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
def maybe_offload_to_cpu(module: torch.nn.Module) -> torch.nn.Module:
device = next(module.parameters()).device

if device == torch.device("cpu"):
return module

global _CPU_OFFLOAD_MAX_BYTES, _CPU_OFFLOAD_BYTES
if _CPU_OFFLOAD_BYTES >= _CPU_OFFLOAD_MAX_BYTES:
return module

pin_memory = is_pin_memory_available()

# offload parameters to CPU
# use pin_memory if possible, which helps cudagraph capture speed
offloaded_parameters = False
for p in module.parameters():
if _CPU_OFFLOAD_BYTES >= _CPU_OFFLOAD_MAX_BYTES:
# we use per-parameter offloading
# one module might have some parameters offloaded and some not
break

# `torch.empty_like` does not support `pin_memory` argument
cpu_data = torch.empty_strided(size=p.data.size(),
stride=p.data.stride(),
dtype=p.data.dtype,
layout=p.data.layout,
device='cpu',
pin_memory=pin_memory)
cpu_data.copy_(p.data)
p.data = cpu_data
_CPU_OFFLOAD_BYTES += p.data.numel() * p.data.element_size()
offloaded_parameters = True

if offloaded_parameters:
original_forward = module.forward

def forward(*args, **kwargs):
module.forward = original_forward
device_state = {
# here we blindly call `to(device)`
# if the parameter is already on the device, it will be a no-op
k: v.to(device, non_blocking=True)
for k, v in module.state_dict().items()
}
output = functional_call(module,
device_state,
args=args,
kwargs=kwargs)
module.forward = forward
return output

module.forward = forward

return module

这个函数只有一个地方调用了:

1
2
3
4
5
modules = torch.nn.ModuleList(
[PPMissingLayer() for _ in range(start_layer)] + [
maybe_offload_to_cpu(layer_fn(prefix=f"{prefix}.{idx}"))
for idx in range(start_layer, end_layer)
] + [PPMissingLayer() for _ in range(end_layer, num_hidden_layers)])

cpu offload 的整个流程可以概括为:

  1. 将传入的 cpu_offload_gb 读取为_CPU_OFFLOAD_MAX_BYTES
  2. 然后在构建 Module 时对每个 Layer 里面的参数从前往后依次塞到 CPU 的 PIN_MEMORY 上,并累加参数大小 (_CPU_OFFLOAD_BYTES += p.data.numel() * p.data.element_size(), 即参数数量 x 字节大小) 到 _CPU_OFFLOAD_BYTES,直到超过用户配置的可 offload 大小。
  3. 替换对应 Module 的 forward 函数,新的 forward 函数和原来的区别就是:在每次 forward 的时候,将 CPU 上的参数复制到 GPU 上,计算完后再释放

一些可能的理解难点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
if offloaded_parameters:
original_forward = module.forward

def forward(*args, **kwargs):
module.forward = original_forward
device_state = {
# here we blindly call `to(device)`
# if the parameter is already on the device, it will be a no-op
k: v.to(device, non_blocking=True)
for k, v in module.state_dict().items()
}
output = functional_call(module,
device_state,
args=args,
kwargs=kwargs)
module.forward = forward
return output

module.forward = forward

这里的 forward 替换设计挺有意思:

  1. original_forward = module.forward):保存模块原始的前向传播函数到变量 original_forward,为后续恢复原始实现做准备。

  2. (在 forward 函数内的 module.forward = original_forward):在自定义的 forward 函数内部首行,当函数被调用时,立即将模块的前向传播方法恢复为原始方法。这一步确保 functional_call 能够使用原始的前向传播逻辑,避免递归调用。

  3. (在 forward 函数内末尾的 module.forward = forward):前向传播完成后,再次将模块的前向传播方法设置回自定义的 forward 函数,确保下次调用时仍能触发这个包含参数加载逻辑的自定义函数。

  4. (函数末尾的 module.forward = forward):为了确保每次都能 hook 到 cpu -> gpu 这部分的逻辑 (参考原 PR 的解释:https://github.com/vllm-project/vllm/pull/6496/files#r1682069375)

这里的核心是使用 functional_call 配合临时的 device_state,实现参数从 CPU 到 GPU 的动态加载。初看可能会疑惑为什么没有将参数从 GPU 释放回 CPU 的逻辑,其实这是因为 device_state 是个局部变量,函数执行完毕后其引用的 GPU 张量会自动被 Python 的垃圾回收机制释放。而原始参数依然保存在 CPU 内存中,从而实现了内存优化的目的。

同时还有一个小知识点:

  1. 对于 nn.Module, .cuda(), .cpu().to(device) 方法都是就地操作,因此可以直接 model.cuda()
  2. 而对于 torch.Tensor , nn.Parameter 这些,.to(device), .cpu(), .gpu() 方法本质都是创建一个新的 Tensor,原始 Tensor 不变,因此也必须使用 a = b.cuda()

一个实现自动网页剪藏的工作流

2025-01-30 18:31:18

针对现有的软件,我的一些不满意的点

在试用了一众 “稍后读” 和 “剪藏” 软件后, 仍然找不到满意的软件。

总结了一下针对当前的「网页剪藏」软件,现在自己不满意的几个点:

1 - 快照使用「服务器采集」: cubox, pocket 等

这种方式的缺点就在于,很多内容平台,特别是国内的平台,都需要登录才能访问完整内容。

那么这样的采集方式受限就很大,服务器端采集无法携带用户的认证信息,因此往往只能获取到部分内容或者直接无法访问。

2 - 自带的「网页解析」或者「解析为 Markdown」: obsidian / upnote 等笔记软件的 clipper

网页结构和样式的多样性使得通用解析器很难完美处理所有情况,特别是对于代码块、表格、数学公式等特殊格式,解析质量往往不尽如人意。

3 - 仅能保存 URL 的「稍后读」: raindrop 等

内容的持久性在互联网上并不能得到保证,特别是:

  • 一些平台会定期清理历史内容
  • 某些文章可能因各种原因被删除或修改
  • 网站本身可能关停或改版
  • 付费内容可能从免费变为收费

对于一些时刻可能下架的敏感内容,或者经常变动的网站,url 可能过一阵子就看不了了。

我理想中的「网页剪藏」

  1. 我希望功能不要过于复杂,例如各种各样的划线标注,笔记,阅读状态,都是我不需要的。
  2. 我想让我的保存,查看能够尽可能的简单高效,不要打乱我目前浏览网页的进度 —— 也就是说不要在我保存的时候出现一个大表单让我填写,或者卡住等待插件处理和解析网页
  3. 我希望能永久保存内容,在任何时候我可以看到「我当初看到这个网页的样子」。
  4. 我希望做到云存储,不希望作为一大堆散落的附件保存在本地。

于是我搭建了一个联动 SingleFile, dropbox, notion, 和 telegram bot 的 workflow 来满足我的需求。

我搭建的工作流

之所以说是工作流 Workflow,实际上就是将一大堆 API 串起来。

整体实现起来不难,把所有 API 写好,作为一个 notepad 喂给 cursor,基本上 10 分钟就完成了,然后接下来用了几个小时来完善整个流程。

以下是 Demo (youtube):

PS:demo 实际展示了 Telegram 的提示过程 + 服务端日志,实际上对于我而言只需要按一下「cmd + shift + s」 快捷键之后,剩下的一切都是服务端处理了。

PPS:演示的时候 DeepSeek 又卡了,还好加了一个备用的 API。。

简单来说这个工作流如下:

  1. 利用 singlefile 保存单页 html 作为快照。
  2. 通过 webdav 上传到服务端开始处理。
  3. 服务端上传文件到 dropbox 并获取分享链接 (可以直接点击查看快照页面) 。
  4. 服务端通过 deepseek / gpt 的 API 自动生成摘要和 tag。
  5. 将所有的元数据上传到 Notion Database 进行保存。
  6. Telegram 的 bot 全程通知进度,以及报告成功 / 失败的错误日志。

总体来说个人还是挺满意的,最近搞了不少自动化,这个算是折腾比较久的和比较好玩的,于是写出来分享下。

一些录音转文本(STT)的方案总结

2025-01-29 19:26:37

这几天有个需求,就是将一些中英文对话录音文件转文本,然后进行一些分析。所以就调研了一下录音转文本的方案。顺便总结一下 MacOS 上的录音/录屏方案。

STT 全称 Speech-to-Text,即语音转文本。一般有两种常见的需求:一种是将实时语音转文本的识别(流式处理),一种是将录音文件转文本。本文主要记录后者的一些方案。

1 - 录音转文本的方案

自部署方案

目前最常见的模型是 OpenAI 的 Whisper 模型

使用起来也非常简单,只需要两步:

1
2
3
4
5
# 安装
pip install openai-whisper

# 使用
whisper --model base --language zh --output-dir ./output ./input.m4a

目前一些其他的「自建」方案基本都是基于 OpenAI 的 whisper 模型: HuggingFace 的 Whisper 模型

这种自部署的优势就是安全,缺点是需要消耗本地算力,如果算力不足会比较慢,其次感觉 Whipser 对于纯英语识别来说比较好,但是对于中英文混杂的环境,效果还是比较差的。

API 方案

用几家大厂的 API 方案, 比如:

  • OpenAI 的 Whisper API

目前价格是每分钟 $0.006:https://openai.com/api/pricing/

OpenAI Whisper Price

其实这个价格也不算便宜了,转一个 1 小时左右的文本就要 3 RMB 左右。

  • Google 的 STT API

这个更贵,目前价格是每分钟 $0.016:https://cloud.google.com/speech-to-text/pricing

Google STT V2 Price

更多的一些厂商:

服务提供商 服务名称 收费标准 参考链接
微软 Azure Azure 语音服务 按秒计费,包含标准和自定义语音转文本,以及按字符计费的文本转语音服务 价格详情
百度智能云 语音识别服务 支持预付费包和后付费阶梯计价,根据语言模型类型定价 价目详情
阿里云 智能语音服务 一句话识别按次计费,录音文件按时长计费,语音合成按字符计费 费用说明
腾讯云 语音识别 实时识别和文件识别按日使用量计费 计费概述
华为云 语音交互 按调用时长收费,支持按量付费和套餐包 服务价格
Sonix 转录服务 按小时收费或月度订阅制 价格详情

总之,API的方案确实很方便,但是价格其实都并不便宜。

于是最后我还是将眼光瞄向了国内的一些在线平台。

在线平台

主要试用了科大讯飞的讯飞听见,阿里的通义听悟和字节的飞书妙记。

用起来体验如下:

阿里:通义听悟

Link: https://tingwu.aliyun.com/

阿里通义听悟在中英混合文本识别方面表现最为出色,且提供充足的免费额度(赠送500小时, 除此之外每天签到都可以领取 10 小时)

活动界面

然后功能方面做的也很好,包括内容摘要,发言人识别,文本替换,AI改写等比较好用的功能。

界面

字节:飞书妙记

Link: https://www.feishu.cn/product/minutes

飞书妙记也还不错,每月提供300分钟免费额度,勉强也还够用。

但是如果额度一旦用完,价格就有点贵了,只能付费升级到「飞书 Plus」来用不限时。

price

飞书妙记:界面

另外界面做的也挺朴素的,功能不像阿里的那么丰富,但是比较简练,作为常用的会议记录的用途也足够了。

科大讯飞: 听见

每月仅提供20分钟免费额度,识别效果也相对一般,充值也很贵,39.8RMB/月。

性价比较低。

2 - MacOS 录音方案

作为录制会议或者演讲等用途,经常有一个需求就是想同时录制 麦克风 + 系统声音.

这里介绍一下我现在用的两种能获得录音音频的方式:

1 - 使用 Screenshot 录屏

录屏的话 MacOS 的 Screeshot(截屏)可以直接做到:

  1. cmd + shift + 5 打开录屏,在选项中选择「麦克风」即可。
  2. 录制结束后获得一个 .mov 视频文件。
  3. QuickTime Player 打开视频文件,在「文件」菜单中选择「导出为音频文件」,即可获得一个 .m4a 音频文件。

2 - 单纯录音

有时候我们不被允许录制屏幕,这时候只能通过单独录音的方式,而 MacOS 自带的 “QuickTime” 录音只能单独录制麦克风声音,这时候就需要用到其他手段。

BlackHole

一种方案是利用 BlackHole,BlackHole 作为一个虚拟音频设备,可以将 macOS 系统的音频输出捕获并重新路由,使其既能被录制软件录下又能通过扬声器播放出来。具体操作可以看:知乎:macOS使用BlackHole录制系统声音的同时输出声音

QuickRecorder

另一种就是通过安装其他应用的方式,一般用的最多的是 obs,但是 obs 太大了,搞起来比较复杂和麻烦。

于是我找到了一个使用起来还不错的很轻量的开源软件「QuickRecorder」:https://github.com/lihaoyun6/QuickRecorder

QuickRecorder

使用这个软件录制出来会生成一个 .qma 文件

qma 这种文件包格式可以容纳 2个音频文件,以及一个属性文件。

使用 QuickRecorder 内置的 QMA 播放器打开 .qma 可以同步播放系统声音和麦克风声音,并且可以独立调节音量(调节之后,音量属性会被记录在文件信息里,下次打开这个文件还是这套音量配置)。如果不需要分享的话,直接用 qma 格式保存在硬盘上就行了。如果需要发给别人,qma 播放器自带导出功能,可以将两个音频文件按照用户设定的音量比例混缩成一个普通的单轨音频文件 .mp3 / .m4aref