阅读视图

发现新文章,点击刷新页面。
☑️ ⭐

Neovim conceal机制导致markdown语法隐藏的问题

conceal 是 Vim/Neovim 中一个用于优化显示效果的机制,它可以将某些语法符号替换为更简洁的视觉表示(或完全隐藏)。这在 Markdown、LaTeX 等格式中常用于提升可读性,但有一个问题:不太好确定自己的markdown标签是否写完了,因此markdown文件可能最后少了一个```,渲染结果出错。而对于我来说,我不需要在编辑器里面看到最终的渲染效果,所以这个功能完全可以去掉。

为了确定是插件导致的还是Neovim自带的功能,我用下面的命令打开不启用所有插件的Neovim:

1
nvim -u NORC /path/to/file

结果markdown渲染正常,因此确认问题是由某个插件引入的。

然后可以通过一个个启用插件的方式,来验证是哪个插件导致的问题,但由于插件太多,太麻烦,我问了DeepSeek R1,验证下面的语句可以解决这个问题:

1
2
let g:vim_markdown_conceal = 0
let g:vim_markdown_conceal_code_blocks = 0

也有人说是indentLine设置导致的,但没有再验证。

也是通过这次搜索,了解了conceal这个术语。

☑️ ⭐

GitPod简单使用说明

GitPod是一个云端开发IDE,可以访问gitpod.io,绑定GitHub账号后打开GitHub上的任意项目,也可以通过安装浏览器插件,直接在GitHub网站打开IDE。

GitPod打开后默认是个VS Code在线环境,有一台国外的容器可以使用,机器配置如下:

  • CPU: 16核,型号AMD EPYC 7B13
  • 内存:64G
  • 存储:30G

由于它的服务器在国外,因此可以快速下载GitHub, Google Drive或Hugging Face上的一些模型,然后用Python开一个简单的网页服务(python -m http.server),再在本地用wget下载模型,速度还可以。

GitPod主打的一个点是快速启动开发环境,可以通过在https://gitpod.io/user/preferences 设置中指定dotfile来设置启动环境

这个dotfiles仓库可以保存你常用的rc文件等,保证熟悉的环境能够快速上手,例如我将自己的常用配置放到https://github.com/vra/dotfiles,开机就能用上熟悉的开发环境了。

总之,GitPod可以作为一个免费的临时服务器和在线IDE,偶尔用用还不错。

☑️ ⭐

博客新计划

AI技术日新月异,能用AI做的事情越来越多。

作为一个普通人,知识和技能唾手可得,记忆性的东西不再重要,而独特的思维方式则是你区别于别人的重要标签。在这样的时代背景下,每个人越来越需要独立思考的能力,因此每个自己的独特想法、见解都值得被记录下来。

而作为一个blogger,也许在未来(或现在?),利用你的博客内容,AI可以重建你的思考方式,针对每一个新的事件,AI会给出你的评价,然后在跟自己真实的看法进行对照,是不是很有意思呢?。

基于上面的思考,我决定事无巨细地在这个博客中更新自己的技术内容,包括看到的技术内容引用,简单的comments,尝试新东西的过程,阅读技术代码的历程,造轮子的步骤,等等,总之就是不论大小,一概记录,相信当内容积攒越来越多后,基于这个博客的语料数据,结合我编写的代码,AI能够准确地重建一个我的程序员分身,这样未来也许我就不需要写代码了哈哈。

☑️ ⭐

TransformerEncoder导出onnx问题解决

1. 问题说明

在使用Pytorch的TransformerEncoder时,导出onnx会将时序长度固定,导致没法采用变长输入,例如下面的简单例子复现了这个问题:

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
55
56
57
58
59
60
import torch
import torch.nn as nn


class SimpleTransformer(nn.Module):
def __init__(self, input_dim=512, num_layers=6, nhead=8):
super().__init__()
# 创建Transformer编码器层
encoder_layer = nn.TransformerEncoderLayer(
d_model=input_dim,
nhead=nhead,
dim_feedforward=2048,
dropout=0.1,
activation="relu",
batch_first=True, # 使用batch_first格式
)

# 创建Transformer编码器
self.transformer_encoder = nn.TransformerEncoder(
encoder_layer, num_layers=num_layers
)

def forward(self, x):
# 输入形状: (batch_size, seq_len, input_dim)
x = self.input_proj(x)
output = self.transformer_encoder(x)
return output


# 实例化模型
model = SimpleTransformer(input_dim=512, num_layers=2, nhead=8)
model.eval() # 设置为评估模式

# 创建示例输入(batch_size=2, seq_len=10, input_dim=512)
dummy_input = torch.randn(2, 10, 512)

# 导出ONNX模型
torch.onnx.export(
model,
(dummy_input,),
"transformer_encoder.onnx",
do_constant_folding=True, # 优化常量折叠
input_names=["input"], # 输入节点名称
output_names=["output"], # 输出节点名称
dynamo=True,
)

print("ONNX model exported successfully!")

# 验证导出的模型
import onnxruntime as ort
import numpy as np

dummy_input2 = torch.randn(2, 11, 512)
ort_session = ort.InferenceSession("transformer_encoder.onnx")
outputs = ort_session.run(
None,
{"input": dummy_input2.numpy()}
)
print("ONNX output shape:", outputs[0].shape)

导出onnx时采用的时序长度是10,验证时采用时序长度11,运行时会报错:

1
2
3
4
5
6
7
8
9
10
2025-01-29 14:17:25.266794 [E:onnxruntime:, sequential_executor.cc:516 ExecuteKernel] Non-zero status code returned while running Reshape node. Name:'/transformer_encoder/layers.0/self_attn/Reshape_4' Status Message: /Users/runner/work/1/s/onnxruntime/core/providers/cpu/tensor/reshape_helper.h:47 onnxruntime::ReshapeHelper::ReshapeHelper(const onnxruntime::TensorShape &, onnxruntime::TensorShapeVector &, bool) input_shape_size == size was false. The input tensor cannot be reshaped to the requested shape. Input shape:{11,2,512}, requested shape:{10,16,64}

Traceback (most recent call last):
File "/Users/ws/export.py", line 63, in <module>
outputs = ort_session.run(
^^^^^^^^^^^^^^^^
File "/Users/ws/miniforge3/lib/python3.12/site-packages/onnxruntime/capi/onnxruntime_inference_collection.py", line 266, in run
return self._sess.run(output_names, input_feed, run_options)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
onnxruntime.capi.onnxruntime_pybind11_state.RuntimeException: [ONNXRuntimeError] : 6 : RUNTIME_EXCEPTION : Non-zero status code returned while running Reshape node. Name:'/transformer_encoder/layers.0/self_attn/Reshape_4' Status Message: /Users/runner/work/1/s/onnxruntime/core/providers/cpu/tensor/reshape_helper.h:47 onnxruntime::ReshapeHelper::ReshapeHelper(const onnxruntime::TensorShape &, onnxruntime::TensorShapeVector &, bool) input_shape_size == size was false. The input tensor cannot be reshaped to the requested shape. Input shape:{11,2,512}, requested shape:{10,16,64}

尝试了Pytorch 2+ 提供的TorchDynamo-based ONNX Exporter(torch.onnx.export增加dynamo=True参数),也是同样的报错。

2. 如何解决

这个问题在Pytorch的GitHub 上有几个issue都在讨论,并且也给出了解决方案,不过不知道为什么官方一直没有集成修复代码。

修复方式也比较简单,修改torch/nn.functional.py中的两行代码即可。具体操作如下。

首先定位到当前python环境的functional.py的路径,采用下面的一行命令即可:

1
python -c "import torch, os; print(os.path.join(os.path.dirname(torch.__file__), 'nn', 'functional.py'))"

然后打开这个文件,搜索k = k.view(k.shape[0,只有一处匹配,大概在6200行,内容是:

1
k = k.view(k.shape[0], bsz * num_heads, head_dim).transpose(0, 1)

可用看到这里调用了k.shape[0],在导出onnx时被固定了。将这一句修改为

1
k = k.view(-1, bsz * num_heads, head_dim).transpose(0, 1)

同样的,搜索v = v.view(v.shape[0],也只有一处匹配,紧接着上面的代码,原始内容:

1
v = v.view(v.shape[0], bsz * num_heads, head_dim).transpose(0, 1)

修改为

1
v = v.view(-1, bsz * num_heads, head_dim).transpose(0, 1)

保存文件,再运行上面导出和验证onnx的脚本,一切正常了。

这种方式需要修改Pytorch源码,还是不太方便的,换一个环境,换一个机器,都得操作一遍,希望官方早日解决这个问题。

3. 相关Issues

☑️ ⭐

2024年终总结

2024年是幸福的一年,因为每天有可爱女儿的陪伴,正如此刻,她在旁边吃着山楂棒,看着我打下这行字。

父母回老家了,大家庭变成了小家庭,我们也在3月份搬进了自己的房子,老婆在家全职带娃,我上班离公司更近了,骑电瓶车15分钟到公司,大家都皆大欢喜。

工作内容也从纯视觉算法变化到了多模态算法,语音文本图像,都需要考虑。这种任务其实很有意思,更接近真人处理问题的情况。但难度也不小,未来继续加油吧。

平时上班,周末大部分时间都在陪娃,自己可支配的时间大大减少,因此写博客和开源项目上没太多产出,总共写了个8篇知乎文章,2个开源项目,一个是关于实时图片驱动人头项目,基于快手LivePortrait坐了一个实时版本的封装,另一个是基于LLM给代码仓库打分网站,可以在这里访问

第二个项目其实是一个基于AI驱动的产品尝试。由于AI能力的不断提升,写代码或者说技术壁垒成为一个门槛很低的事情,许多以前没法做的东西,现在在AI的帮助下可以很快地实现,例如那个项目中的Vue代码,完全是大模型不断地根据我的要求生成的,工作的很好。所以我觉得未来成功的产品是体现在创意上,目前来看似乎还没有那个AI产品有很好的创意而引爆C端市场。希望未来有更多的创客借助AI创造出精彩的产品。

这一年也是不断思考人和AI关系的一年,从实际问题到哲学命题,AI与人类的关系,我觉得在未来几年也会一直被讨论。但无法忽视的事实是,AI的能力提升飞快,已经在很多方面超过了顶尖的人类了。从Assistants,到Copilots,再到Colleagues,再到Critics,再到Twins,这种快速的关系变化可能从根本上改变人类对自己的认知。相信在2025年,还会有更多精彩被创造,希望在这个exciting的时代,能做出自己的一点贡献。

出游与相聚

1月18日,农历腊月八,初中同学真林结婚,我提前一天坐飞机回家,参加完婚礼下午坐飞机回来。这个陪我度过最后一个单身夜晚的好朋友也结婚了🤣最近可爱的女儿也出生了。

1月28日,云亮结婚,我们回家参加婚礼,然后彤彤和乖乖去庄浪,我回公司继续干活。

1月31日,栾京来杭州出差,我们张凯一起去湖滨银泰吃火锅。

2月8日,腊月二十九,要过年了,我先坐高铁到天水,到汽车站时,已经没有回庄浪的班车了。在汽车站外等了会,也没找到会庄浪到车,只能先坐出租车到秦安,再看怎么办。天水的出租车司机又坑了我一把,说好的的走高速,结果还是沿着低速缓慢走,不诚信的行为再一次上演。到秦安已经天黑了,有点饿,等了半天也没找到车,只能在秦安高铁站的天桥下,找了个卖釀皮的小摊,围着蜂窝煤炉子吃了点东西。之后找到了私家车,拉着四个往庄浪方向的人出发了。到庄浪已经晚上8点半。正月初三回我家,又是一番人在囧途。春节结束后,2月19日,也就是正月初十坐飞机回杭州。

3.月1日 团队去西溪源谷开年会,垂钓,飞盘,烧烤,抽奖,k歌,放烟花……

3月20日搬家,从22年年中搬到九堡,终于又回到了余杭。彩虹和龙哥从南通过来参加我们的搬家活动。

3月30日周末,小家庭去西溪湿地春游,在大树下睡了半天。

4月5日清明节,我们去桐庐吃桐庐菜,游富春江,爬富春山,负重20斤的小baby登顶富春山东西二个钓台,俯瞰富春江,有点意境。这过得非常舒服的一个假期。

5月1日劳动节,我们去苏州了,住在吴趋坊附近,夜游平江路独有一番风味,从商场出来的小巷一直走到平江路,人潮拥挤,小店林立,文创美食目不暇接。别的虎丘山,山塘街,泰伯庙,北寺塔,阊门,平门等大大小小的景点,护城河中缓缓驶过的游船,真的很有江南的感觉。还有商场的各种美食,吴趋坊的烤肉,真的美味。

5 月23日-5月26日我和几个同事去西安参加CCIG会议。参会之余和高中室友魏朝奇于参聚会,我们数年没见了。也和栾京一家吃了烧烤,然后去大唐不夜城,走路到地铁站回去。上次见他们还是去榆树参加他们的婚礼。

6月21-6月22日两天,小团队去千岛湖outing,吃鱼,K歌,烧烤,摘杨梅。

7月1日去富阳考驾照,科二挂了科三过了,7月21日重考科二和科四,拿到驾证。从5月5号开始练,总共耗时两个半月。

8月31日,我们去版本馆,上次来是版本馆刚开放的时候,天气炎热,没有深度看展馆内容。

9月7日,我们去玉鸟集玩,在玉鸟雕塑的草坪上坐了很久,有些惬意。然后去旁边的村民食堂吃饭,接着去单向空间大屋顶,单向空间自由阅读的感觉很棒。

9月15日,打车去下斗门村,在村北面拐角的时候,整个田野突然出现在眼前,仿佛走进了宫崎骏的田园世界。我们沿着河堤走到下陡门村网红树,休息后再走回北塘春池,玩了会吃了土菜,味道不错,然后打车回家。

9月17日中秋节,下午去杭师大北面的大草坪露营地等月亮升起。夜晚月亮从东边楼房上面探出头,然后往中天走。我们和月亮合影,然后点了水饺外卖,吃完才回去。

国庆节请了2天假,9月28先到天水,包叔顺路送我们到武山,第二天回家。10月3日云亮和明霞送我们到庄浪。由于10月2号晚上我们去k歌,大家都是食物中毒了,国庆接下来的几天都特别难受。

11月2日, 我们去良渚古城遗址公园,水稻黄了很好看,还有秋风送来远处好听的歌声,循着歌声而去,发现是有稻香音乐会,在草坪上听了会,然后去看了日落,又大又红又圆,真的是难以忘怀的一天。

11月3日,再次去西溪湿地,在老地方铺了垫子吃东西,拍照。

12月1日,和东升夫妇和东升妈妈一起去吃了兰木肆,东升也换工作了。

12月13日大团队爬九曜山,游净慈寺,第一次爬西湖西南角的山。

12月27日小团队年末聚餐,去吃铁锅炖,感觉吃的比之前好吃多了。

读书

《乔布斯传》
《创造:用非传统方式做有价值的事》
《李飞飞自传》
《一地鸡毛》
《万物皆计算:科学奇才的探索之旅》

影视

你想活出怎样的人生
年会不能停!
飞驰人生2
阿索卡
内景唐人街
老练律师
谜探路德维希
豺狼的日子

☑️ ⭐

谷歌Gemini和Gemma大模型的Python调用

1. 说明

Google 发布了Python 包google-generativeai,可以方便地调用Gemini和Gemma 系列的模型,免费模型只需要申请一个Key,无需任何费用。

而且Gemini 1.5 Pro模型还支持一些多模态任务,例如检测bbox,实际测试下来效果还不错。
这里简单写一个流程,体验效果。

2. key获取与包安装

访问Google AIStudio 来进行Key注册:Google AI Studio
Python包安装:

1
pip install -U google-generativeai 

3. 文本输入

简单使用大模型的对话能力,例如讲一个鬼故事:

1
2
3
4
5
6
7
8
9
10
# pip install -U google-generativeai
import google.generativeai as genai
import os
import PIL.Image

# obtain your key at https://aistudio.google.com/
genai.configure(api_key=os.environ["GOOGLE_API_KEY"])
model = genai.GenerativeModel('gemini-1.0-pro-latest')
response = model.generate_content("讲一个鬼故事")
print(response.text)

输出结果:

最后一句有点惊悚…

4. 多模态输入

随便找了一张跳舞的人的图片,测试一下人体框检测效果,这里使用Gemini-1.5-pro来多模态检测人体框:

prompt如下:’Return bounding boxes of the , in the format of [ymin, xmin, ymax, xmax]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# pip install -U google-generativeai
import google.generativeai as genai
import os
import PIL.Image

# obtain your key at https://aistudio.google.com/
genai.configure(api_key=os.environ["GOOGLE_API_KEY"])
model = genai.GenerativeModel('gemini-1.5-pro-latest')

# output bbox
img = PIL.Image.open("dancer.jpg")
prompt = 'Return bounding boxes of the dancer, in the format of [ymin, xmin, ymax, xmax]'
response = model.generate_content([img, prompt])
print(response.text)

检测结果:

5. 参考

  1. google-generativeai · PyPI
  2. Building a tool showing how Gemini Pro can return bounding boxes for objects in images (simonwillison.net)
  3. Explore vision capabilities with the Gemini API | Google AI for Developers
❌