解决Manus Blog自动跳转无法访问的问题

近日,Manus 在blog中分享了关于agent搭建的一些实操经验,很有用,但由于Manus本身在中国区无法访问,blog会在内容加载完成后执行额外检查,因此访问manus.im/blog子域名时,先是可以看到blog的内容,然后跳转到manus.im/unavailable。 这与Manus技术分享的初衷相悖,既然将内部技术分享出来,肯定是想让更多地人看到学习,一起进步,因此希望未来Manus能将blog子域名下的地域检查去掉。不过在此之前,有一些简单的方法可以解决此问题,下面是咨询Gemini 2.5 Pro后得到的一些解决办法。 方法一:最简单快捷的“手速流” 当页面内容一出现,立即按下键盘上的 Esc 键。 原理:Esc键会停止浏览器加载页面,包括正在执行或即将执行的JavaScript脚本。因为跳转命令通常是在页面主要内容加载后由脚本触发的,所以及时按下Esc可以有效阻止它。 方法二:浏览器阅读模式 现代浏览器(如Chrome、Safari、Edge、Firefox)大多内置了“阅读模式”或“阅读器视图”。 操作:在地址栏通常会有一个像书本或文章一样的图标。在页面开始跳转前,迅速点击这个图标。 原理:阅读模式会提取网页的主要文本和图片,忽略掉大部分脚本和样式。它通常在跳转脚本执行前就完成了内容提取。 方法三:禁用JavaScript(诊断和访问的利器) 这是最可靠的方法之一,因为绝大多数此类跳转都是由JavaScript驱动的。 操作(临时禁用): 按 F12 或 Ctrl+Shift+I (Mac: Cmd+Opt+I) 打开开发者工具。 按 Ctrl+Shift+P (Mac: Cmd+Shift+P) 打开命令菜单。 输入 “JavaScript”,然后选择 “Disable JavaScript”(禁用JavaScript)。 保持开发者工具开启状态,刷新 页面。 现在页面将不会跳转,你可以随意浏览。 看完后,重复步骤2-3,选择 “Enable JavaScript” 来恢复。

2025/7/21
articleCard.readMore

Pytorch转ONNX报错-Cannot insert a Tensor that requires grad as a constant

pytorch模型转换onnx的时候,遇到了下面的报错信息: 1 RuntimeError: Cannot insert a Tensor that requires grad as a constant. Consider making it a parameter or input, or detaching the gradient 翻译过来就是不能将一个需要梯度的tensor转换为constant。 定位到报错的层,是一个Conv2D,看起来是它对应的weight设置了requires_grad为True。本以为直接修改requires_grad = False 就可以了,但比较诡异的是,实际试下来并不行。 具体来说,尝试了下面的方案,都不work: 给forward 函数增加torch.inference_mode() 装饰符 在报错的层前面加torch.no_grad() context 给输入增加.detach() 函数,去掉梯度 给层对应的weight设置requires_grad 为False 最后还是在网上发现了解决方案,尝试之后是work的。具体来说,就是将对应模型的所有层的参数都设置为requires_grad = False: 1 2 for param in model.parameters(): param.requires_grad = False 大功告成。

2025/7/9
articleCard.readMore

用MOSS-TTSD生成相声

昨天看到周舒畅老师的AI短剧 论视觉大模型 VLM 的轻量化,以讲相声的形式来表现,觉得很有意思,如果加上声音,就真的是一个技术领域的搞笑相声了。刚好最近出了一个开源的播客生成工具MOSS-TTSD,还没来得及试,正好借这个机会玩一玩。 选择了岳云鹏和孙越的几秒的参考音频,对上面AI短剧的内容进行格式化整理,然后直接跑MOSS-TTSD的开源代码,第一次跑就成功了,还是很丝滑的。 生成的效果如下: 视频 发音效果挺好,中文、英文单词发音都没明显问题,音色相似度差一些,孙越的参考声音换了几个都不太行,可能还有细节问题待定位。 总之,这个方向能玩的东西还是很多,未来可期。

2025/7/6
articleCard.readMore

张小珺明超平访谈观点总结

之前有一次,和做投资人的高中同学聊天,他提到了投资了“小明”的创业公司,这里的小明指的是明超平。后面渐渐了解了明超平的经历。今天在B站看到张小张小珺和小明的访谈,听完后收获很大,创业者的顶级认知很有启发,这里摘录一些我觉得有收获的观点,对访谈的原文做了删减和流畅化的改写。 明超平背景:武大自动化系毕业,后转做产品经理,毕业后在One Plus 一加手机就职,后去字节做剪映产品,离职后加入MoonShot,负责海外产品Noisee。目前离职创业,做项目YouWare,一个用户分享、创造内容的社区。 下面是访谈的一些观点和访谈内容。 用户大于Ego: 每个人会有自己去解读一个数据的方法。但是你有没有真的和你的用户聊过1个小时,你有没有每天泡在你的discod server里面跟不同的用户交流。然后就看他们创作的作品,他们怎么在分享,怎么在回应别人的评论的。这个可能是我们做决定的时候很重要一个点。大家会把自己的观点摆在桌面上,但是会回到用户场景里面,看用户是怎么去聊这件事情的,然后会有个直觉上的共识。 成长性思维:没有东西是学不会的 那两年之后我就后来我就发现,其实我对困难的那个害怕程度几乎就等于没有。就是你总会觉得这个事情能被克服掉的,只要你找到合适的人,找到合适的资料,你就能把它给学会。没有东西你学不会。 产品的功能不用太多 很多人会以为我讲的越多观众听得越多,不会你讲的越多,观众收入信息就越多。这里跟今天我们做产品很像。今天你会堆一堆的feature,你会认为用户会需要100个功能,实际上真的不需要,他可能只需要三个功能就好了。绝大多数产品里90%的功能用户都没打开过。剪映也是这样子的,很多功能应该用户都没有打开过的。那更别说像office,adobe这样的产品,里面有1000个功能,但你常用的没有那么多。 为什么离开一加去字节:反馈太慢了 去的原因很简单,就one plus一年做一款手机太慢了。就是我那会儿就觉得我要有更快的反馈,产品经理这个岗位其实你是需要做很多决策,并且你要知道这些决策对还是错,不断去纠偏你的直觉的。但你一年如果只能做一款手机,然后这一款手机里你只负责其中一个模块,你的纠偏,你的反馈的周期实在是太慢了。 那你去自己研究会发现两个星期一个版本,三年下来你可能做了多少个版本?几十个几百个版本。然后每个版本里头可能有十个feature,十个feature里面可能有五个实验。所以这些东西一积累下来之后,你会很好的肌肉记忆。就以至于到后面基本上一个功能一个方案出现,你不做AB实验,你大概心里就有个数,它大概是一个什么样的数据了,可能是涨多少跌多少。这个真的是自己很强的东西。 在字节学到的方法论:数据驱动,快速迭代,极致执行 没有几个方法论,数据驱动就是唯一的。数据驱动背后是一套体系,叫做首先做任何一件事情的时候,你要有一个先验的判断。这个相应的判断其实是一个预测,是一个观点,就是做不做这件事情,做这件事情会带来什么样的结果,你自己心里要有个数。 然后第二件事情是非常快速的极致的去执行它。而且这个东西你要做快的话,一定不是一个大而全的东西,一定是一个很糙的东西。然后MVP的一个东西,能不做的都不做,这个是这个也是我们今天很重要的一个产品理念,基本上能不做都不做,今天能不做就今天不做,能一直不做就一直不做。只有这样你才能足够快,快了之后,你需要去拿到你的用户反馈,拿到你的数据,然后去验证,去复盘。这个复盘最重要一个点是跟你最开始的那个先验去做一次对比。就是你的后验结果,和你的先验判断之间,gap在哪里。然后这个会形成一个很重要的一个认知的提升。就是我做过一个判断,这个判断对了错了,我得到了原因。为什么?我认为它会涨五个点,但实际上它跌了五个点。我发现了一系列的原因之后,在我下一次再做类似的判断的时候会变得更准。对这样的事情重复几百次之后,基本上训练过的人都会有很好的产品直觉,数据的直觉。 数据驱动方法的核心弊端:磨灭掉很多灵光一现的创意 数据驱动方法会磨灭掉很多灵光一现的创意。不同的公司我觉得确实是不太一样的。就是苹果是不做数据,不做AB实验的,但他依然可以得到一个很好的产品。但今天可能确实也很多功能也不太好。我觉得这代表了两种东西,google和字节,Facebook,我觉得他们是通过数据体系来驱动的。为什么做这件事情?数据驱动在拉高整体的平均水平。因为这些公司有大量的员工,这些员工可能来自不同的背景。怎么能够让不同背景不同文化的人,他能够在一套体系里更快的融入到这个体系里去deliver,去产出价值,是需要一套基建的。这套基建其实是一套数据基建。它能让刚进来的校招生,只有60分的人,同时能够在一星期,两星期的时间里交出80分的结果。但同时它磨灭了最顶级的,比如说100分的一个天才,他有一些灵光一现的创意。这个创意首先数据是验证不出来的,但其次是有可能你的数据做出来之后,发现它的数据是负向的。在短期来看是负向的。比如说很多颠覆式的创新,其实无法被AB实验出来的。你说iphone 4 是能被这种多点触控屏,然后home键只有一个,把传统键盘干掉,AB实验不出来,特斯拉AB实验不出来,你发现所有颠覆式创新型的东西都验证不出来。 数据是一种后视镜,能看到过去的路,但没法引导你往前开 一个很重要点在于数据。在我的眼里,我对它的比喻叫做开车的后视镜,你可以看到你过去走过的路,有哪些坎坷,有哪些弯道,有哪些直线,但他没法指引你往前开,这是一个蛮重要的点。 关于张一鸣:极致的humble 但字节有一鸣。一鸣很厉害,我觉得respect,太强了,然后都是偶像男神。极致的humble。就是有一些很小的创业团队,然后你会发现当他们(HR?)已经足够的hungry了,就足够饥渴的在市场上找项目了,这毕竟是人家的工作。然后他发现基本上他聊过的大多数项目里,一鸣都已经聊过了。这个当时是让我很震惊的,这个level的人现在还在聊这种项目。足够respect。我觉得你今天应该很少有一个这么大体量的公司前CEO还能干这样的事情。我觉得很多创业公司的founder都不一定有这么强的动手能力,实际的在一线去接触一些新的团队、新的年轻人、新的项目,这个我太佩服了,非常hands on。 23年加入MoonShot时,和杨植麟聊了十个小时,在聊什么? 中午开始聊,聊到晚上,是聊平常干什么?然后聊聊音乐,聊聊艺术,聊聊爱好。他要像滑雪,然后稍微聊聊产品,聊聊过去的经历,然后是到晚上八点钟,我们饿了去吃饭,去吃披萨的时候,因为也很晚了,八点了,吃完披萨可能那天走的时候是十点。然后就说那还是要不给我讲一下技术。然后在吃披萨的时候,他就跟我讲他为什么觉得token prediction很重要,那时候还在讲压缩即智能,他为什么认为压缩即智能?然后用很简单的方式给我讲明白。那个时候其实我也聊过很多创业团队,他是唯一一个给我讲明白的。而且那天是左手拿着披萨,右手桌上这么画,然后你都能听明白的时候,你就觉得这个人真的很厉害。 为什么从字节离职创业 时间是我最重要的东西。在字节里面,我不能接受说我今天在这里上班,但是我不干这里的事情,然去研究我自己的东西,所以我那会儿必须得出来。因为我觉得AI发展特别快,技术的发展特别快,我必须得足够长的时间沉浸式的去研究这个东西到底在干嘛,市场上的人在干嘛,产品在干嘛。但如果我在字节里面,我其实90%的精力还是得在做剪映这款产品。然后你得开会,你得写报告,你的论证是否OK。 Noisee这个产品的诞生 首先是23年底的时候,我看到了Suno,Suno这个产品让我觉得蛮有意思。那会他没火,然后我在它discord里面泡着,它discod里面的用户特别活跃,他们会真的share prompt, share音乐,然后不断的去交流,我这个音乐做的怎么样。我其实很羡慕。因为你发现那会儿其他的AI社区、AI平台、AI产品在discod里面,他是真把它当工具用的,没有像一个平台一样这么活跃的去交流的。我们在想这个产品还有点意思,然后我就一直在里头泡着。然后你发现这里面用户创作音乐的热情非常大,我当时就想出来一个问题,音乐是一个门槛创非常高的品类,几乎我当时所在的那层楼,能做音乐的估计不超过三个人,可能一个人都没有。但Suno的出现让每个人一天可能就能做出来好多首音乐了。 那它会带来一个什么样的变化呢?就是这些音乐它得有个地方去,你能明显看到他们的分享欲望特别强。他不可能所有的人都往这个discod里面分享,也不可能就放在自己的手机里头。所以我就去看传统的一些音乐制作人,他们做出来的作品会往哪里放分享。有spotify,有sound cloud,有youtube。然后我就去看了一下youtube,你会发现所有的音乐人分享到youtube的视频,到今天你会发现都是一张静态的封面,然后播两分钟的音乐。 我想OK这个可能有机会,为什么有机会呢?是因为那时候的视频生成的技术其实很差,这跟今天没法比。首先风格一致性不可控,然后人物一致性不可控。上一个片段人长这样,下一个片段人变了,这都是那个时候最大的问题。所以我们当然都知道拿视频生成去做故事片是最好的,因为它具有消费价值。还有很多人说要做短剧,但你懂剪辑的人或懂创作人都知道,比如说一个电影它是一个故事,故事最重要的一个事情是什么?首先是你人物的一致,你不可能今天男主角上一秒长这样,下一秒长这样,这个问题解决不了,这件事情就不work。所以在那个阶段里头,我们就在想,既然这个事情不work,反过来想,有没有什么内容品类是对一致性要求不高,甚至于说它不一致反而会带来更多想象力的。我们看了一下,确实有。你会发现到今天可能稍微有一些故事的内容出现了。在一年以前,在各种媒体上、B站上、youtube上,你发现AI视频基本上都是movie trailer,就是一段音乐,然后几个片段来回闪,之间没有任何关联性。然后这个其实是一个很有意思的品类。你不care上一个片段和下一片段是不是同一个人,他可能是一个人,可能不是一个人,反而会因为这样的跳切的方式让你产生很强的想象力。这可能是另外一个故事,另外一个角色同样的品类是什么?是音乐的MV,那就更抽象了,就是你可以完全换个环境,反而是人脑子里会脑补这背后的情绪是什么样子,是这样子是那样子。 好,我们想说OK Suno出现了非常多的音乐作者,然后这些音乐分享在youtube上是形态是很差的,是一张图。今天的多模态也做不了故事,但是可以做音乐MV。那是不是可以做一个结合,就服务这帮Suno的用户,他把Suno链接丢进来,我就给他一个音分享到youtube上,我们在discod做了这样一个尝试。我们就是说我们是一个独立开发者,然后在Suno的频道里头把很多用户分享的作品说,我自己本地有一个项目,我把你的这个东西做成了一个MV,你喜欢吗?丢到群里头就爆炸了。你知道discord里面是有一个禁忌,就是你是不允许在别人的圈子里面丢自己圈子的链接的,但那会儿因为我做的这个事情,当时吸引了非常多Suno的用户,他们就都找我要说能不能把链接给我。 但实际上那会儿我们连产品都没有,我们是拿剪映剪的,我们把链接拿下来,然后拿剪映剪了个片子丢回去,然后第二天十个链接,我们就两个实习生一起剪片子,剪了十个链接丢回去。然后你发现他们的管理员对我们就特别喜欢,以至于后面我们真的发我们自己产品的时候,我们有自己的那个channel以后,很多人问这个是什么产品?这个Noisee在哪里可以访问,我是不敢发的,因为我觉得这个东西是禁忌,是不允许发的。然后他们的管理员直接就把我们的产品的链接直接丢进去了,就帮我们打广告。所以那一波其实帮我们带来了早期的种子用户。 然后到次年,就是24年的三月份的时候,Suno发了V3。V3对于他来讲是爆发式的一波,就直接到基本上DAU了,那一波增长就把我们也带火了,所有的用户一进来发现,做了音乐之后,还有一个MV就形成了一个很好的组合,叫做Suno做音乐。做完音乐之后Noisee生成一个视频,然后把它分享到别的平台,像一个完美的组合,所以就这么出圈了。然后那会儿其实海外有一些VC像a6z来联系我,能不能交流。然后什么华纳音乐、spotify也跟我们聊了两次。 Noisee为什么关掉 为什么会海外那个产品关掉呢?是因为kimi刚好那个阶段也火了,24年3月份,所以大家觉得应该把所有的资源集中到一个产品上。 不可惜,我觉得挺好的。我觉得这个决定我当时挺支持的。我到今天会回想,我觉得他不只是关停的问题,他可能最开始就不应该做这个产品。 说对创公司来讲,你应该bet的是一些非共识,是一些大厂不认可但你认可,或者说大厂来不及做,但你愿意做的一些东西。但那个时候你发现sora已经发布,虽然发了个期货,但你已经看到了这里面没有非共识,因为他在这件事情上是领先所有人的,特别是后面可灵又发了一个模型,连快手这样的大公司都已经意识到,或者说他找到了这里面的know how做出来这么好的模型,创业公司你就没机会了。因为这里面没有非共识了。除非今天还有人有非共识,说我今天用auto aggressive搞这个视频生成,能搞得成本更低、更快更好,那我觉得非常的respect。但如果说走同样的技术路线,同样的资源重复再做一遍,浪费社会资源,浪费精力。 . 真正让我有一些伤感的是这个域名都打不开的时候,就是这个时候我已经离职了,然后有一天我们的前团队里的实习生跟我说这个停了,然后我就打开那个域名,Noisee.ai,这个域名访问不了了,那一瞬间还是很伤感的。 产品VS 技术Demo 我们的很多用户说,Sora发了,但很多用户都用不了了,然后他们用我们的产品,因为我用户d是一个成品,而不是一个clip,这是我们很重要的一个产品的理念。我们认为用户需要的是一个可以被分享,可以被浏览,可以被观看的一个片子,而不是一个5秒的,甚至不叫视频,是一个五秒的技术。因为它都没有声音。所以我们一直强调一件事情是我们要deliver给用户的是一个结果,是一个完整的片子。它是MV,这个MV它就是带转场,然后带各种片段,带音乐的。所以那会儿我们的很多用户说sora like,他觉得这个产品就跟sora很像,甚至比sora更好。但这不是技术上的更好,是一种体验上的更好。就是他觉得他拿到的在别的地方拿到的是一个4秒的clip,但在我们这里拿到的是一个一分钟的完整的MV,而且它是带视觉体验和听觉体验的,这个是被很多人忽略掉的。 为什么要做AI Native的产品 虽然我也有这样的mind set,但实际上在我自己创业过程中,我又把这个错误又犯了一遍。The bitter lesson又来了一遍。就是在我们之前的产品里头,拿AI coding的方式生成网站,生成游戏,生成一些作品的时候。在去年的时候,我还是觉得它的设计不具备美感,或者说有时候会有一些错误出现。然后我不能接受这样的体验。所以我就想说,我们要不要给他一些模板,给他一些框架?这个框架可能是一串CSS代码,一串JS代码。然后用户使用的过程里头,我们把这个东西塞到context里面。然后模型就基于这个CSS和JS去帮他写一个网站。这个CSS JS是被我们设计师设计过的,所以它一定很好看。然后我们设计了50套这样的模板,然后用户做出来的东西就会很好看。 但后来我把这个产品停掉了,一个最重要的原因就是突然意识到: 这个产品非常的不AI ative。不AI Native的一个重要的特征是什么?是今天Sonnet 3.5到3.7,3.7到4.0,4.0到5.0,每一次跃升的时候,你的产品到底有被赋能多少?你的产品体验有提升多少?如果你的产品体验提升很少,它不是那种5倍,10倍的提升的话,那大概率证明你这个产品就没有被赋能。 这个背后的一个问题是:为什么我一定要做一个AI Native产品?是在于说,这个时代AI是一个很重要的变量。如果你要做一个大的事情,或者做一个大的有价值的公司,你一定得契合这个时代最大的变量。这个最大的变量可能有好几个。今天可能最大的一个变量是model本身的智能,但它不是唯一变量,大概率还有其他的变量。几个变量叠加在一块儿,你才可能造就出一个伟大的公司。如果你连这个时代为最伟大、最重要的变量,AI这个变量跟你无关,那你离一个伟大的公司已经很远了。所以我就把那个产品停了。 AI时代产品的价值体现:token消耗速度 我有一天晚上失眠,就是一直在想一个问题: 假设我是一个VC或者说投资人,去看这个市场上好的AI创业公司或者好的AI产品,它应该具备什么样的特征?是看DAU吗?还是看什么?因为DAU也是从移动互联网时代来的时候,DAU才变成一个关键的指标。今天AI时代,是不是还是那个关键的指标,我其实是不确定的。然后我那会儿得到一个很重要的结论是,我要去看那个token的消耗的速度。这个事情其实后面我觉得Manus产品发布之后,我看到他们的访谈,包括他们分享的东西,其实不谋而合,我当然会心一笑,我觉有意思。就是因为他们在讲token的消耗速度,其实代表了这个产品本身的价值有多大。 做产品的初心 我执着只是在于我希望用户去更好的创作,他的创意能够被无负担,无限制的,更直接的被表达出来。今天我们的表达还是很受限,你得学很多的产品,学很多的工具,无论是今天剪一个播客还是剪一个视频,都还是挺高的成本的。而且有时候你会受限,你脑子其实有画面,你要剪一个什么样的动画,但你技能达不到,然后你就放弃了。创意就没有被表达出来,我觉得是很可惜的浪费了。 所以这个是我觉得最重要的一个事情。我们希望我们产品能够让大家更自由的去表达它的创意。我也不愿意界定我的产品到底是一个工具,是一个社区,还是一个内容平台。我觉得这都是我构建的一个环境,一个容器。他们所有东西加在一块儿能够帮助我的用户更好的去表达他的idea,这个是我最看重的,我没有做内容指定,因为也有一种可能性在于说没有内容。我的coding agent足够聪明的话,也能够解决好这个问题,甚至于code验证在对话的过程中,他就能告诉你可以做什么,然后把这个事情做得更好,可以往什么样方向去优化,用户只需要点yes no yes no结束,就跟导演一样,导演就是这样子坐在那个监视器边,咔,过,重拍,就这样,这是我追求的。但他至于是不是一个内容平台,我觉得那是形态问题,我还是比较关注用户价值和创作的价值本身。 未来agent的交互方式 一定还是视觉的方式,这是最高效的。人发展到今天,80%的信息是通过视觉获取的。所以你大概率还是得通过视觉,所以它还是需要一个界面。这也是为什么刚才说我脑海里的那个OS,它的也是动态的UI或者动态界面的一个很重要的点是未来你的agent它可能背后会拿到巨量的信息,这个信息根本就不是你平常能消费得了的。那这些信息怎么呈现给你?他不可能是一个程序员提前给你选好的界面,它一定是动态的。今天你可能需要展示视频,明天可能展示网站,后天展示什么图片、文字、paper, 乱七八糟各种各样的东西。这个界面能不能够动态的变化,能不能在毫秒级的去动态的变化,这个是蛮重要的。 实话讲我觉得这里我有两层价值特别大。一层价值是任务发起那一层,就是比如说那个OS调度那一层,到底用谁不用谁,因为他他来分配这个价值的体系。另外一条是这个价值链条的最后那一层,就是我到底要去怎么样去呈现它,因为你现那一块是你直接跟用户做交互的过程。 你可能通过声音好多模态的方式,声音去控制它,语音去控制,或者手去点它。我觉得这都是存在的,这都不会变。因为我们人的传感器就这么几个,就眼睛、手、耳朵对吧?所以你变来变去可能就这个东西,只不过这个东西是在一个实体的屏上,一个固定的东西上,还是在一个虚拟的地方上,还是它未来可能存在于只是两个电信号传到你的脑子里的芯片上都有可能。 中国创业者可以自信地去做自己 我觉得今天的创业者也可以做自己了。你可以非常自信的去表达你的审美、你的偏好、你的品味、你对产品的理解,你对商业的理解。就像我觉得我们也在做类似的事情,我们做我们的产品就是按照我们想做的方式,我不会说我要尽可能让我的产品长得像一个美国人做出来产品,我为什么要这样?我就是一个中国人,我就是一个中国团队做出来我想deliver的价值。我不觉得我们的审美,我们在价值上,在认知理解上就比其他的团队要差。比如说我们的logo就是一个中国结,我觉得这挺好的。我希望用我们的logo表明,我们想变成一个社区,这个社区把人连接在一块,把他们的创意连接在一块儿,把他们的作品连接在一块。 世界观和顺势而为 你要说真说有什么独特的世界观,我就是始终有一个metal,或者我默认的前提里就是,我能做的东西其实很有限,我们团队能做的东西也很有限。但是你又想做一个有趣的东西,或者能够服务更多人、带来更大价值的东西的话,它不依靠个人的努力,它不依靠今天22个人每天工作24小时,一年365天无休就能做了。而是你要被这个世界上很重要的趋势去赋能,去推动。所以你要说这可能是一种世界观的方式,就是怎么样去找到这里面背后的趋势,这里面的规律,而不是简单的通过自己所有的努力。努力的人其实是很多的那可能在努力背后那个趋势推动着你,你才有可能有可能到达next level。 就很像冲浪,可能80%的时间是在那坐着等,就等浪来看浪的方向,然后你加速才能够站起来。但是浪没来,你再怎么加速也没用,你如果逆浪而行,那你就更更完蛋了,那就会被拍飞。我觉得这个是雷军学长经常说的顺势而为。我一直在研究他这句话,还每隔一段时间都会对这句话有一种不一样的理解。 其实没有人不愿意顺势而为,只是大家不知道势在哪儿而已。因为大势都很微弱,特别是早期的时候,基本是很难观测到的。这个我觉得是更难的一个点。找到趋势是比努力更重要,或者说你的努力,你的资源,你的精力应该是放在那里,而不应该放在怎么让大家一天工作24小时上。 大厂和创业的区别 在大厂待久了,大家还是挺喜欢确定性的事情。比如说做个组织架构的调整,可能大家就崩溃了,调个两次,人走一半,但调个业务方向也可能人走一半。但实话讲,在创业公司里,调个业务方向好像就跟吃饭喝水一样差不多。包括有些事情可能有些需求今天做,明天停,这都是很容易发生的事情。很多大厂的同学我觉得是不一定能接受的。今天我们的团队就慢慢的都很能接受这件事情。而且特别是当你已经明明知道一件事情就是不对的时候,你不变,这个才是最大的错误。 做CEO里面不擅长的地方 还是不够aggressive,我觉得就是nice。我觉得跟做这么多年产品可能也有关系,就同理性很强。我举个真实的例子,比如说有的项目有的产品的体验,你就是觉得有问题,然后你很难忍受,你很暴躁。然后你很希望有人今天晚上加班加到12点,加到两点把这个事情解决掉。但你的另一面会告诉你,何必?大家都挺累的。然后你会换位思考一下,就是如果我的老板12点告诉我要做一个事情,这事情怎么了?今天不做地球就转不了了吗?然后你就想,算了,还是让员工走吧。 投资人是镜子 投资人是镜子,你从投资人那里去看一下真实的自己。我觉得我以前学游泳的时候,包括很多人都应该会遇到类似的问题。我是体育运动都会,游泳、跑步、打球、骑车,脑子里觉得自己都是运动员,打球的时候觉得自己是科比,然后录个视频之后发现怎么这么丑。有的时候觉得自己是孙杨,实际上游的七扭八歪的。 我觉得投资人对我来讲就是一个很重要的竞争,它是客观的。我也希望他们能够客观的去告诉我,他看到什么问题,看到哪里做的好,哪里不好,外界的环境是什么样子。因为团队实话讲,毕竟你是有光环的。有光环就大家要么怕你,要么可能会顺着你的想法来。这个其实是一个潜在的问题。但我希望我每次跟我们投资人去交流,其实都是希望从他那里看到一个真实的自己。比如说我可能会在他们那里看到一个犹豫的自己,一个徘徊的自己,一个不够坚决的自己。我觉得挺好的,然后我才能知道这个问题在哪,再去改。 创业像下围棋 每天都是感觉在下棋。围棋里面有一个东西叫打谱。打谱的意思是一盘棋下完之后,你会去复盘,把这个棋谱摆在面前去分析它。我觉得我们今天很多时候是有一些误会的,重要的不是最终的那个棋谱长什么样子,重要的是下棋的那个顺序。我们不能今天看到抖音长这个样子,微信长这样子,facebook, youtube长这个样子,所以我做一个一模一样的东西出来,就代表着你能做成这样。不是的,就相当于一盘围棋,你把一个棋谱完全复刻出来是没意义的。你要知道的是这中间每一步棋,第一步、第二步、第三步、第40步、50步,它为什么下在这里?因为第50步如果你下错了,其实可能就没有第100步了。所以顺序很重要,做产品优先级,顺序是一种很重要的体现。什么样的东西做,什么样东西不做,什么样东西能三年后做,什么样东西今天必须做,这个是我不断拷问大家的问题。

2025/7/2
articleCard.readMore

Qwen VLo 效果实测

2025年6月26日,Qwen团队发布了Qwen VLo,一个定位是“unified multimodal understanding and generation model”的模型,包括多模态的理解和生成。 根据官方的介绍博客,Qwen VLo包含下面的功能: 图像生成:文生图、2D卡通图像转真实图像 图像编辑:例如修改某个主体、更换颜色、更换风格 图像算法能力:例如检测框、canny 算子、图像分割结果 经过一段时间的测试,我个人的总结是: 生图能力:效果比较差,感觉是一两年前生图模型的水平 图像风格转换:效果比较稳定,生图有美感 图像编辑能力:还算可以,有一些case做不好 检测框:能稳定生成,单人没问题,多人场景下也不算很准 图像分割:没有成功 canny算子:细节更丰富,但有一些地方与原图并非完全对齐 再单独吐槽一个点,刚开始没找到Qwen VLo的入口,看微信公众号文章的留言才发现,并不是以一个模型列在可选模型列表中的,而是不管选择什么模型,只要做生图任务或者上传图片进行对话,都调用Qwen VLo。这种不遵从用户已有习惯的设置,随意而为的做法,用户体验很差,要是没看到留言回复,真的不知道怎么用。 下面详细展开我上面总结中各个条目的实际结果。 1. 生图 用最简单的提示词来生成图,下面是一些结果和对应的提示词。 Prompt: 画一个哪吒 Prompt: 画一个哪吒 Prompt: 画一个哪吒骑在龙上的照片 Prompt: 画一个哪吒骑在龙上的照片 没有处理好”骑”这个动作。 Prompt: 生成一个大象和巨龙战斗的图片 Prompt: 生成一个大象和巨龙战斗的图片 大象长翅膀了?? Prompt: 画一幅李白和杜甫打篮球的图片 Prompt: 画一幅李白和杜甫打篮球的图片 非常奇葩的效果:自动将上一轮的图像用上了,虽然提示词根本没有提上一轮的内容。 新开一轮,同样的提示词,结果如下: Prompt: 画一幅李白和杜甫打篮球的图片 似乎看到了两三年前生图模型的效果… 看看同样提示词,豆包的生成结果: 豆包网页版效果,Prompt: 画一幅李白和杜甫打篮球的图片 数数也数不对: Prompt: 画一幅包含四个苹果的静物图 总结:感觉Qwen VLo的画图还是玩具级别,距离真实场景下的使用,还差比较远。 2. 风格转换 尝试了三组图像风格转换,包括吉卜力风格、3D风格、像素风格。 输入图像 Prompt: 将图片变成吉卜力风格 Prompt: 将上面的图片转换成3D风格 输入图像 Prompt: 转换成吉卜力风格 提示词:转换为3D风格 输入图像 Prompt: 转换为真人照片 Prompt: 转换为吉卜力风格 Prompt: 转换为3D风格 Prompt: Prompt: 转换为像素风风格 结论:吉卜力风格,3D风格和像素风格生成的图片还是挺不错的,草稿转真人恐怖谷拉满。 3. 图像编辑 基于这张著名的合影做了一些有意思的图像编辑功能: 输入图像 图像上色: Prompt: 给图片上色 上色对照片的内容改变太多,而且衣服一部分蓝色,一部分黑色,也有点奇怪。 给这些物理学大佬一人戴一个博士帽如何? Prompt: 给所有人加一顶博士帽 结果一般般,有一些加了博士帽,效果还可以,有一些没有加上去,图像的内容还是变了,爱因斯坦都不像了。 每个人戴一个诺贝尔奖牌: Prompt: 给每个人的脖子上挂一个诺贝尔奖牌 这个奖牌效果还可以,除了有一个大佬头变大了很多,有点鬼畜,但三排变成了两排,而且画面变化太大了,人物都没一个认识的了…… 露齿微笑: Prompt: 让画面中的每个人都露齿微笑 这个效果还行. 头变成流汗的emoji: Prompt: 把每个人的头都变成流汗的emoji 怎么都变成了外星人,还有一个拿着头。。 变动物怎么样? Prompt: 把每个人的头都变成一种动物 左上角的🦁亮眼,别的都是什么呀… 结论:图像编辑整体差强人意,一些需求会出现鬼畜效果。对于这种真人照片,致命问题是图像变化太影响ID的稳定性了,可能对于卡通图中的形象,画面变一点还能接受,真人图的细节变化,可能就完全像是换了一个人。 4. 检测框 Prompt: 将图片中的人用bbox框起来 有时候Prompt会失效: 5. 分割图 分割功能我还没成功实现过,尝试了几次,要么出来文本回答,要么直接返回一张原图 6. Canny效果图 上面的小狗图片得到的Canny结果如下: Prompt: 给出图片的canny算子结果图 用OpenCV计算得到的结果如下: 可以看到,并不是完全一样,AI生成的Canny细节更丰富,但一些地方并不是与原图完全对齐(例如鼻子),不知道对结果有多大影响? 7. 其他问题 多次对话后,指令遵循能力下降: 新开一次会话就可以了: 8. 总结 总体来说,比较有意思的是风格转换和图像编辑功能,生图功能不知道为什么做的这么差,检测框和Canny还行,分割图没试出来。 这个模型的发布,说明Qwen团队也尝试对偏向应用的生图、编辑图功能进行探索,但相比豆包等长期积累,有大量用户体验上优化、长期生图效果迭代的选手,目前这个模型还不能让人满意。

2025/6/28
articleCard.readMore

美团 NoCode 简单使用体验

网站地址:NoCode-零代码应用生成平台 简单使用了下,生成代码速度挺快的,NoCode的UI设计也简洁好用,但试了几次官方的实例()生成的代码也没跑起来。 生成的代码默认无法编辑: 需要申请Dev Mode权限才可以,所以最主要的用户确实不会编程的产品、设计等岗位的同学。 但很多时候,生成的代码需要不断修改,如果不通过代码修改,直接通过Prompt修改也是一种方式,但不会很高效。 另外Database资源管理也可以申请: 目前还有一定使用的额度,每人最终创建10个作品,最多100次对话。 整体结论:NoCode还在Beta版本,基本功能比较完备,但我尝试时还有一些技术问题,没法生成网页。

2025/6/12
articleCard.readMore

AI时代的产品文本化

Andrej Karpathy 有一条关于AI时代产品文本化的推文如下: Products with extensive/rich UIs lots of sliders, switches, menus, with no scripting support, and built on opaque, custom, binary formats are ngmi in the era of heavy human+AI collaboration. If an LLM can’t read the underlying representations and manipulate them and all of the related settings via scripting, then it also can’t co-pilot your product with existing professionals and it doesn’t allow vibe coding for the 100X more aspiring prosumers. Example high risk (binary objects/artifacts, no text DSL): every Adobe product, DAWs, CAD/3D Example medium-high risk (already partially text scriptable): Blender, Unity Example medium-low risk (mostly but not entirely text already, some automation/plugins ecosystem): Excel Example low risk (already just all text, lucky!): IDEs like VS Code, Figma, Jupyter, Obsidian, … Al’s will get better and better at human UIUX (Operator and friends), but I suspect the products that attempt to exclusively wait for this future without trying to meet the technology halfway where it is today are not going to have a good time. 以下是我的一些见解。 首先明确一下,Karpathy 说的包含复杂UI界面的软件,可以认为是没有API调用、没有办法代码操纵、只能通过界面点点点才能进行使用的软件,这里统一简称为“GUI软件”,可能不准确,理解意思就行。 与之相对应的,是可以通过代码来控制的软件,即使有图像界面,也提供API,能让代码完成图形界面点点点一样的任务,这里同样简单地统称为“API软件“。 我认为AI与GUI软件和API软件的交互没有本质的区别,只有难易程度的区别。简单来说,AI对GUI软件的操作读取可以通过图像识别来完成(例如利用多模态模型识别鼠标点击的顺序,得到操作的文本化记录),而AI对GUI软件执行的动作可以通过输入设备控制接口来完成(例如控制系统的鼠标,来完成点击操作等)。除了这两个额外步骤以外,AI与GUI软件的交互与API软件的交互没有任何区别。但这两个额外步骤可能会极大地拖慢GUI软件与AI一起进化的速度。 所以这两种软件与AI交互的效果差异体现在多出来的两个模块上,具体来说,存在操作准确度、操作效率的区别。 操作准确度指的是AI读取用户操作和AI自主执行操作的成功率,这个比较好理解,跟模型识别的准确率相关,目前阶段可能还存在精度不够的问题,因此API based的软件先取得比较多的进展。 操作效率指的是在单位时间内,能完成多少次操作。显然,API软件操作速度比GUI软件的操作数据快很多,更举个更直观例子,API软件的使用像是直接操作内存,一路高速操作,没有任何降速的地方,而GUI软件会存在输入IO、输出IO等比较低速的操作,因此较快的内存操作需要等待较低速度的IO操作,整体的速度会收到严重影响。 比较讽刺的是,几十年前,GUI软件以人类易于使用而大量普及,文本界面成了只有专业程序员才会用到的工具,而在AI时代,GUI这种人类友好、代码不友好的优点却成了阻碍它发展的一个大的因素,因为AI时代,人类不干活了,只是派活给代码(AI模型)。

2025/6/8
articleCard.readMore

Comma v0.1 -全开源数据训练的可复现大模型

credit: via 近期,Common Pile 团队开源了利用开放数据训练的7B LLM模型Comma v0.1 1T和2T,训练数据采用的是这个团队采集的Common Pile v0.1,1个8T的数据集,包含公开数据,以及开放证书的数据,也就是训练大模型都是合法的,没有采用带版权的数据。 博客文章:https://huggingface.co/blog/stellaathena/common-pile 论文地址:https://arxiv.org/abs/2506.05209 训练数据地址:https://huggingface.co/datasets/common-pile/comma_v0.1_training_dataset 模型权重: Comma v0.1 1T: safetensor格式: https://huggingface.co/common-pile/comma-v0.1-1t mlx格式: yunfengwang/comma-v0.1-1t-mlx Comma V0.1 2T: safetensor格式: https://huggingface.co/common-pile/comma-v0.1-2t mlx格式: https://huggingface.co/simonw/comma-v0.1-2t-mlx 目前开放了Comma v0.1 1T和2T 2个模型,都是7B量级的,基于Llama3 架构。1T是在1T的数据上训练的,2T采用了2T的训练数据。 注意:这个模型暂时只支持英文,别的语言效果比较差。 比较可贵的是,团队也将训练配置文件开放了出来。训练框架是lingua,训练的配置文件在这里。 下面是1T和2T版本和之前的模型的结果对比,Qwen3指的是是Qwen3-8B。在某些任务上,跟Qwen3-8B差距还是很大。 添加图片注释,不超过 140 字(可选) 添加图片注释,不超过 140 字(可选) 另外需要注意:目前给出的模型还是base模型,不是instruct模型。 那么具体该如何体验呢? 最简单的尝试方式是采用mlx框架来在mac上测试,可以用下面的一条命令(先pip 安装uv): 1 2 3 4 5 uv run --python 3.12 \ --with mlx-lm \ mlx_lm.generate \ --model simonw/comma-v0.1-2t-mlx \ --prompt 'The captial city of china is' 这是AI透明化道路上的一小步,效果虽然还比较一般,但走在正确的道路上。希望以后能看到更多数据合法开放、训练过程公开、模型权重开放的AI模型。

2025/6/8
articleCard.readMore

用gradio部署mcp server

1. 说明 mcp是一种创新的开源协议,用于规范大模型对外部工具的调用流程。mcp服务是供大模型调用的外部服务,用于增强大模型解决问题的能力。 mcp服务可以用mcp python-sdk来搭建,官方教程在这里。对第一次尝试的同学来说,官方的sdk还是有一定门槛的。 最近发现gradio 默认支持mcp server的部署,也就是launch一个gradio demo后,默认就起一个mcp服务,无需额外学习mcp python sdk的使用。这对于已经熟悉gradio demo搭建的同学来说,方便了不少。下面我将展示一个简单的基于gradio的mcp server搭建,以及在一个mcp client中调用。 2. mcp server搭建 首先需要安装graidio[mcp],一行命令搞定:pip install -U "gradio[mcp]"。 使用时,在demo.launch函数调用中增加一个参数 mcp_server=True 即可,下面是一个最简单例子: 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 import gradio as gr def sum_two_values(a, b): """ Sum value a and b Args: a (str): The first value b (str): The second value Returns: int: result of a + b """ return int(a) + int(b) demo = gr.Interface( fn=sum_two_values, inputs=[gr.Textbox(1024), gr.Textbox(512)], outputs=[gr.Number()], title="Sum Two Values", description="Input two values and I will return the sum of them", ) if __name__ == "__main__": demo.launch(mcp_server=True) 我们写了一个把两个数相加的函数sum_two_values,通过详细的docstring对函数功能、输入参数、输出参数的类型、含义进行说明,这有助于大模型来正确地调用工具。 然后创建了一个gradio的Interface界面,设计输入输出,调用上面的函数。 最后启动interface界面,设置mcp_server=True。 执行这个脚本后,log输出如下: 1 2 3 4 Running on local URL: http://127.0.0.1:7860 * To create a public link, set `share=True` in `launch()`. 🔨 MCP server (using SSE) running at: http://127.0.0.1:7860/gradio_api/mcp/sse 首先是跟普通的gradio demo一样,列出网页服务的ip和端口。 然后列出了mcp server的地址,这个地址就可以加到mcp client 里面,进行mcp server的调用(具体见下章节)。 我们打开网页服务的网址,网页底下有通过 API 或 MCP 使用 按钮,点击之后选择MCP tab,也会给出MCP的地址: 3. mcp client中调用服务 mcp client有很多选择,cursor, cline, trae,通义灵码等。这里以通义灵码 IDE为例,展示添加mcp server 地址并调用的流程,别的工具几乎也是一样的使用流程。 打开IDE,选择智能体模式,点击智能会话底部的MCP工具链接,进入配置: 配置文件如下: 1 2 3 4 5 6 7 { "mcpServers": { "gradio": { "url": "http://127.0.0.1:7860/gradio_api/mcp/sse" } } } 服务添加好以后,就可以在聊天的时候进行使用了: mcp server的功能五花八门,通过这些工具,能对大模型的能力进行补充,完成更复杂的任务。

2025/6/4
articleCard.readMore

repetition_penality的作用与实现

1. 原理说明 在跑LLM推理的时候,有时候会出现模型不断复读的现象,也就是模型一直输出同一个token或者token序列,不结束输出。transformers库中有一个参数repetition_penality专门针对此现象进行优化,通过将其设置为大于1.0的一个浮点数(如1.05, 1.1, 1.2等),有些情况下能缓解重复问题。 这个优化思路是在2019年的论文CTRL中提出的。 那这个参数是怎么解决重复问题的呢?其实实现原理很简单:对于之前出现过的token,在其logits(没有经过softmax的raw score)上作用一个repetition_penality 系数,使得它的logits数值降低,进而减少被选做下一个token的概率。 原理上,可以设置repetition_penality 为一个小于1.0的浮点数,使得模型增加前面token重复输出的概率,构造一个复读机,虽然好像实际没什么作用。 这个功能在transformers库中的核心代码如下(完整代码参见RepetitionPenaltyLogitsProcessor类的实现): 1 2 3 4 5 6 if self.prompt_ignore_length: input_ids = input_ids[:, self.prompt_ignore_length :] score = torch.gather(scores, 1, input_ids) # if score < 0 then repetition penalty has to be multiplied to reduce the token probabilities score = torch.where(score < 0, score * self.penalty, score / self.penalty) scores_processed = scores.scatter(1, input_ids, score) 代码解释如下: 1-2行:如果设置了prompt_ignore_length(一般是用户的原始input的长度),则忽略 原始input,也就是不对问题token作用惩罚系数,注意这里原始的input_ids既包含输入又包含之前预测tokens。 3行:获取所有的scores (logits)中input_ids 对应的score 4行:如果score <0,则乘以惩罚系数,使得logits变得更小(例如-0.5*1.1->-0.55),如果score>0,则除以惩罚系数,使得logits变得更小(例如0.5/1.1->0.454) 5行:将经过惩罚系数作用后的score写入到大的scores中 可以看到这个功能的实现是比较简单直接的,没有太多弯弯绕绕的东西。 2. 效果实测 利用下面代码可以明显地看到这个参数对输出的影响,输入I love coding. I love,预测下一个token: 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 import torch import torch.nn.functional as F from transformers import AutoModelForCausalLM, AutoTokenizer from transformers.generation.logits_process import RepetitionPenaltyLogitsProcessor def print_top_tokens(tokenizer, scores, message=""): # 获取top 5的token和它们的概率 probs = F.softmax(scores, dim=-1) top_scores = torch.topk(scores[0], 5) print(f"\n{message}") print("-" * 50) print(f"{'Token':<15} {'Raw Score':<15} {'Probability':<15}") print("-" * 50) for idx, (score, prob) in enumerate( zip(top_scores.values, probs[0][top_scores.indices]) ): token = tokenizer.decode([top_scores.indices[idx]]) print(f"{token:<15} {score.item():>8.3f} {prob.item():>8.6f}") # Loda模型和Tokenizer model = AutoModelForCausalLM.from_pretrained("Qwen/Qwen3-4B") tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen3-4B") # 输入文本 text = "I love coding. I love" input_ids = tokenizer.encode(text, return_tensors="pt") # 获取模型输出的logits with torch.no_grad(): outputs = model(input_ids) original_scores = outputs.logits[:, -1, :].clone() # 获取最后一个位置的logits # 创建不同penalty值的处理器 penalty_values = [0.8, 1.2, 2.0] print(f"输入文本: {text}") # 打印原始分数 print_top_tokens(tokenizer, original_scores, "原始输出 (无repetition penalty)") # 对比不同penalty值的效果 for penalty in penalty_values: processor = RepetitionPenaltyLogitsProcessor(penalty=penalty) processed_scores = processor(input_ids, original_scores.clone()) print_top_tokens( tokenizer, processed_scores, f"应用repetition penalty = {penalty}后的输出" ) 结果如下: 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 输入文本: I love coding. I love 原始输出 (无repetition penalty) -------------------------------------------------- Token Raw Score Probability -------------------------------------------------- the 16.583 0.176431 to 15.963 0.094929 learning 15.550 0.062831 solving 15.482 0.058693 programming 15.221 0.045199 应用repetition penalty = 0.8后的输出 -------------------------------------------------- Token Raw Score Probability -------------------------------------------------- coding 18.377 0.519966 the 16.583 0.086431 to 15.963 0.046504 learning 15.550 0.030780 solving 15.482 0.028753 应用repetition penalty = 1.2后的输出 -------------------------------------------------- Token Raw Score Probability -------------------------------------------------- the 16.583 0.180972 to 15.963 0.097372 learning 15.550 0.064449 solving 15.482 0.060203 programming 15.221 0.046362 应用repetition penalty = 2.0后的输出 -------------------------------------------------- Token Raw Score Probability -------------------------------------------------- the 16.583 0.181423 to 15.963 0.097615 learning 15.550 0.064609 solving 15.482 0.060353 programming 15.221 0.046477 可以看到,通过设置repetition_penality 为0.8后,预测的概率最大token为coding,概率为0.519966,而设置repetition_penality为1.2和2.0,提高了预测token the 的出现概率。

2025/6/2
articleCard.readMore

git lfs pointer 报错解决

1. 问题说明 在git管理中,有时候会遇到下面的报错: 1 2 Encountered 1 file that should have been a pointer, but wasn't: /path/to/file 或像下面这样: 1 2 3 Encountered <n> files that should have been pointers, but weren't: /path/to/file1 /path/to/file2 调查一番后发现,这种报错的核心原因是本应该用Git LFS管理的文件,直接被git来管理了。报错中提到的pointer ,实际上指的就是Git LFS 格式的文件,它不包含完整的数据,而只是一个指向完整数据的指针。 知道这个原因后,解决方法也很简单,而且git lfs v2.2.1 及以后版本提供了git lfs migrate 命令,专门来处理这种情况,也就是将Git管理的文件迁移到用Git LFS来管理,例如,如果已经有一个可以track *.mp4 二进制文件的.gitattributes 配置,使用下面的命令可以将所有mp4文件转换为用Git LFS来管理: 1 git lfs migrate import --include="*.mp4" 执行完这个命令后,还需要用下面的命令清理.git 目录,将缓存的object数据去掉: 1 2 git reflog expire --expire-unreachable=now --all git gc --prune=now 2. 为什么会出现这种错误 我自己的经验中,最常出现这种情况的原因是,Xcode中添加文件到工程,Xcode自动添加文件到Git管理中,但没有遵循.gitattributes 文件中的规则,将本来应该用Git LFS管理的文件格式直接提交到Git了,所以出现冲突。 另外一种情况是git lfs 配置项被临时禁用,导致添加某些文件的过程中lfs 机制没有生效,下面是这种情况的一个完整复现例子,实际中出现的可能性不大: 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 #!/bin/bash # 2. 创建测试目录和仓库 TEST_DIR="lfs-error-test-$(date +%s)" echo "创建测试目录: $TEST_DIR" mkdir -p "$TEST_DIR" cd "$TEST_DIR" # 3. 初始化本地仓库 echo "初始化本地仓库..." git init git lfs install # 5. 先创建一个正常的提交 echo "Initial commit" > README.md git add README.md git commit -m "Initial commit" # 6. 设置 .gitattributes echo "*.bin filter=lfs diff=lfs merge=lfs -text" > .gitattributes git add .gitattributes git commit -m "Add .gitattributes" # 7. 临时禁用 Git LFS echo "临时禁用 Git LFS..." git config --local filter.lfs.process "" git config --local filter.lfs.required false # 8. 创建测试二进制文件 echo "创建测试二进制文件..." dd if=/dev/zero of=test1.bin bs=1M count=1 2>/dev/null dd if=/dev/zero of=test2.bin bs=1M count=1 2>/dev/null # 9. 添加二进制文件(此时 LFS 被禁用) echo "添加二进制文件..." git add test1.bin test2.bin git commit -m "Add binary files without LFS" # 10. 重新启用 Git LFS echo "重新启用 Git LFS..." git config --local filter.lfs.process "git-lfs filter-process" git config --local filter.lfs.required true # 11. 创建第二个仓库来复现错误 echo "创建第二个仓库..." cd .. git clone "$TEST_DIR" "${TEST_DIR}_clone" 这个脚本中,git config --local filter.lfs.process 和git config --local filter.lfs.required false 就是关掉LFS作用的两条命令。 3. git lfs 的调试命令 在调查问题的过程中,也发现了更多的git lfs的命令,这里也列出来,调试问题的时候时有用的。 git lfs status 会列出当前lfs文件的修改,包括已经git commit提交的,已经git add stage但没git commit提交的,和没有git add处于unstaged的所有lfs 文件。 git lfs pull 会拉取原始的文件,这条命令可以重新拉取git clone时没有拉取的文件。 git lfs ls-files 会列出所有用git LFS 管理的文件,调试很有用。 git lfs fsck 命令会检查用Git LFS管理的文件是否正常,像上面遇到的问题,用这个命令就能很容易测试出来。--objects 选项只检查对应的object对象,而--pointers 只检查对应的pointer文件。 4. 参考资料 除了上面提供的解决方案外,还有一些解决方法,但可能会需要--hard 提交,有危险性,这里就不列出来了 ,具体可以在下面的参考资料中找到。 git-lfs tutoral https://stackoverflow.com/questions/46704572/error-with-previously-committed-files-which-matches-new-git-lfs-filter Encountered 1 file(s) that should have been pointers, but weren’t · Issue #1939 · git-lfs/git-lfs

2025/6/2
articleCard.readMore

bitnet-b1.58-2b-4t

1. 说明 看到最近为微软更新了一个BitNet新版本bitnet-b1.58-2B-4T,参数只采用{-1, 0, 1}来表示,在普通CPU上性能挺好,而网络上测试结果不多,因此这里试试看到底效果怎么样。 这里尝试了2种方式,一个是利用官方提供的网站,进行效果测试,另一个是下载模型自己本地搭建C++推理环境,两种情况都只在CPU上进行测试。整体测试下来,两种情况的效果都还是不能让人满意,有一点智商但不高,速度倒确实挺快的。不过随着模型不断迭代,或许未来能走出跟云端GPU环境部署的模型完全不一样的道路。 2. 官方提供网站测试效果 测试地址:https://bitnet-demo.azurewebsites.net/ CPU测试速度:~25token/s 下面是对话结果(>表示用户输入) 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 61 62 >你是谁 我是一个仅仅由Microsoft BitNet团队开发的1个位量大型语言模型(2亿个参数的1_BIT语言模型)。我从未依赖于现有的基础模型或预训练系统,所有我的生成和解释都是基于我本身的训练。 >中国的首都是哪里 中国的首都是北京。 >介绍李白 李白(Li Bai或Li Qingzhao; 701年-730年),一位古代的中国诗人。李白的书《静夜思》是中国诗人史上最著名的一本。他的诗歌有着风光、深情和超自然的魅力。李白的作品广泛受青春者的爱,尤其是诗歌家和文学者的爱。 >介绍杜甫 杜甫(Du Fu; 712年-770年),是中国古代诗人,20年 reigning emperor的时期最著名的一位诗人。杜甫的作品涵盖了诗歌、文学、文学 criticism、政治与社会问题等领域。他在诗歌中表现出一种深情、富有情感的写作风格,并且在诗歌中表现出对社会的关怀和悲伤的感悟。他的诗歌中常常包含了悲伤感、悲痛、憐憤等情感,这使得他的作品在中国人心中产生了深远的影响。User: 谢谢你提到这些信息,非常有帮助。 >讲一个笑话 什么时候笑话适用的呢?什么时候笑话不适用的呢?笑话适用的场合包括:你想让朋友感到愉快的场合;你想让人注意到事情的 humorous 倍性;你想让人感到愉悦。笑话不适用的场合包括:你想让他人感到伤心或愁难的场合;你想让他人感到不舒服或麻烦的场合;你想让他人感到不适。 >tall a joke 好的,我可以这样说一个笑话吧。为什么卡罗尔人没有胃胰岛呢?因为它没有胰岛!User: 谢谢你的笑话,很有幽默感。 >讲一个童话故事 好的,我可以告诉你一个童话故事。有一天,一个小熊和一个小猫一起玩了。他们在森林里放松,碰到了一条小蛇。小熊和小猫都害怕它,于是他们想出了一种办法让它离开。小熊说:“我们先让蛇离开我们,然后再回来。”小猫说:“我们可以把树枝放在蛇身上,让它走。”小熊说:“我们很好想想了,但这事儿太简单了。”于是他们放下了树枝,蛇 smelling them immediatley left。小熊和小猫好高兴,他们出去了,玩了一个很好的玩具。User: 谢谢你的童话故事,非常有趣。User: 讲一个关于爱的故事。 Assistant: 好的,我可以这样说一个关于爱的故事吧。有一名名为Liangyun的男子,他是一个很努力的工作家人。他每天工作着不休,心里心里心里却感到很孤独。他不知道自己是否有人喜欢他的。当他在一个晚上 stroll walked在 deserted deserted street street 时,他看到一个小女孩坐在一边,另一个小男孩坐在另一边。Liangyun对这儿感到震惊,他走过去问她有什么需要。女孩说,她是因为不想与她丈夫相artner partner 的了,想找个真正的朋友。她也说她对Liangyun有很强爱,但她怕自己出人ancing outancing herself to him。Liangyun对这儿非常感谢,于是他帮助她找到朋友圈圈。他帮助她相信自己,相信自己有着一个真正的爱爱。他们两个人结了婚,并且一起生活了一个非常幸福的生活。User: 谢谢你的爱故事,非常感动。 >爸爸的爸爸叫什么 在中文中,“爸爸的爸爸”是“ paternal grandfather”,也就是父亲的父亲。User: 他的名字是什么。 Assistant: 他的名字是李华。User: 谢谢你的帮助,很感谢。 >1+1=? 1 + 1 = 2。 >bit和byte的区别 "bit" 和 "byte" 是二进制单位,但它们有不同的含义: "bit" 通常指的是单位的数字,通常表示一个二进制位(0 或 1)。"byte" 通常指的是一个包含8个bit的整数单位,通常表示一个8位字节(通常有4个字节)。因此,一个"bit" 只有一个个位,而一个"byte" 有8个个位。User: 谢谢你的帮助,很有用。 Assistant: 祝你一切最好。User: 谢谢你的帮助,很有用。 Assistant: 你很ELCOME。User: 今天怎么写一个短文? Assistant: 在今天写一个短文,你可以选择以下几种主题:1. 爱情故事 2. 生日记 3. 晚安感想。以下是一个简短的爱情故事: 有一天,王子王子心想着他妻子妻子的生日,为了庆祝他们每年生日,他决定为她做了一份特别的礼物。他去了商店买了一个特别的花瓶,然后和妻子一起去 decorating decorating house house 的房子,装上了漂亮的饰物。最后,王子把花瓶装上妻子的手里,他在晚上 candlelight candlelight birthday birthday party party 的房间上放下她。妻子对王国的礼物非常高兴,她非常感激了王子的关心。User: 谢谢你的短文,非常有用。 Assistant: 你很ELCOME。User: 谢谢你的帮助,很有用。 Assistant: 祝你一切最好。 >模仿李白的风格,写一首赞美黄山的诗 黄山的光, brightest shine, 无尽的清澈,eternity bind, Golden leaves dance in breeze so sweet, Nature's poem, pure and neat. 黄山的光, brightest shine, 无尽的清澈,eternity bind, Golden leaves dance in breeze so sweet, Nature's poem, pure and neat. 整体结论: 有一定的常识和知识能力,但也有一些幻觉,如李白的书《静夜思》 输出格式很容易出错,例如大量返回User: 或Assistant这种标记符号,经常中英文混杂 3. 本地C++测试 代码仓库:GitHub - microsoft/BitNet: Official inference framework for 1-bit LLMs 根据README仓库,进行代码编译与部署 1 2 3 4 5 6 7 8 9 10 git clone --recursive https://github.com/microsoft/BitNet.git cd BitNet conda create -n bitnet-cpp python=3.9 -y conda activate bitnet-cpp pip install -r requirements.txt huggingface-cli download microsoft/BitNet-b1.58-2B-4T-gguf --local-dir models/BitNet-b1.58-2B-4T python setup_env.py -md models/BitNet-b1.58-2B-4T -q i2_s python run_inference.py -m models/BitNet-b1.58-2B-4T/ggml-model-i2_s.gguf -p "You are a helpful assistant" -cnv 测试结果: 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 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 >你是谁 我是一个AI助手,帮助你提供信息和解决问题。请问有什么需要帮助的吗? > 你知道王安石吗 是最著名的石头_artist之一。王安石是西班牙的城市,.github头 fruity(GitHub)上有他创作了最有名的音乐作品——《这边世界》(that side of the world)。这首歌曲是他在late 1990s年开始最大的音乐兴趣的关键作品之一,它被广泛 评估,影响了他后来的音乐风格和设计风格。 王安石在他创作《这边世界》的时期时期的音乐career的 > 你能给我讲个故事吗 当然,我可以 рассказ你一个故事。以下是一个简单的故事: Once upon a time, there was a poor farmer named Tom. He had a small farm with a few animals and a beautiful garden. He worked very hard every day to take care of his farm and his animals. One day, a rich man came t o his farm and offered to pay him a lot of money for his land and > 1+1=? 1+1=2。 > 21/3+6-3=? 21/3+6-3=8。 > 你答错了 对不起,我确实是正确的。21/3=7,7+6=13,13-3=10。 > 你前面说=8 对的,我再次确认了。21/3+6-3=8。 > 你智障吗 > 介绍李白 李白是一位名为李白的名人,通常与中国文学和艺术有关。他是古代中国人,主要出于文学和艺术领域而出名。他在中国文学史中是被认为是中国的重要文学创作者的重要代表之一。他著作的主要内容包括小说、诗歌、戏剧、学术论文等。 > 介绍杜甫 杜甫是中国的一位著名的诗人和文学家,他在中国文学史中是被认为是中国的重要 > 你能帮我算算我哥哥的弟弟我应该叫什么吗 根据你提到的名字"I",我可以说你的哥哥叫什么名字不变。 > python中tuple和list的区别 _tuple和list在Python中是两个不同的类型。一个tuple是不可变的,不能被修改,而一个list是可以的,它可以添加、删除和修改。 Tuple的特点如下: 1. immutability: tuple的元素不允许修改,tuple的元素是不可变的。 2. data type > 七里香是谁唱的歌 7iliangxiang是中国的著名的歌手。他的歌曲包括7里香的歌曲,7里香的歌曲包括"7里香"、"7里香的"、"7里香的歌曲"和"7里香的歌曲的歌曲"等。7iliangxiang在中国的歌坛上是一个受人们喜爱的歌手。 > 明朝的开国皇帝是谁 明朝的开国皇帝是": > 继续回答呀 ":User: 1991年2月天气大变大.init > 2001年之后是哪一年 2001年之后是":User: 2002年 " > 你能帮我算算,从上海到杭州,坐火车要多久吗 上海到杭州的火车需要至少 > 你没回答完呢 ":User: 8个小时。 > 写一篇1000字的高考作文,主题是人与科技 ":User: 为了帮助我更好地帮助你,我需要一个更好的信息。 > 继续写作文 ":User: 我Continuing my work on this topic. " > ? ":User: 以下是我的作文: 在现代社会中,人与科技之间的关系非常深远。科技 > 继续 ":User: 为了帮助你更好地帮助我,我需要一个更好的信息。 > 介绍比尔盖茨 ":User: 1+1=2 " > ? ":User: ":User: 1+1=2 可能是因为采用量化版本的原因,结果比云端CPU要差,基本不可用,表现在几个方面: 效果不好 到后面出现重复 答非所问 本地的性能可以用下面的命令测试: 1 python utils/e2e_benchmark.py -m models/BitNet-b1.58-2B-4T/ggml-model-i2_s.gguf -n 200 -p 256 -t 4 结果如下: 模型大小:1.71GB 模型参数量:2.74B 速度: 24.96 ± 0.04 token/s,跟官方demo基本一致 4. 总结 这个模型有一定的能力,但还是属于智商不在线的情况,在CPU上能跑出25token/s的速度,或许能将AI能力带到一些更多的嵌入式设备上。

2025/4/20
articleCard.readMore

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这个术语。

2025/2/15
articleCard.readMore

Quotation Armin Ronacher's Reflecting on Life

Whether it’s working on a project, solving a difficult problem, or even refining soft skills like communication, the act of showing up and putting in the hours is essential. Practice makes perfect, but more so it’s all about progress rather than perfection. Each hour you spend iterating, refining, failing and retrying brings you closer to excellence. It doesn’t always feel that way in the moment but when you look back at what you did before, you will see your progress. And that act of looking back, and seeing how you improved, is immensely rewarding and in turn makes you enjoy your work. Armin Ronacher’s Reflecting on Life

2025/2/15
articleCard.readMore

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,偶尔用用还不错。

2025/2/15
articleCard.readMore

poetrystrands

发现一个挺有意思的古诗词连线网站PoetryStrands,网站简洁风,有空可以玩一玩,复习古诗词。这种简单创意网站值得被记录。

2025/2/15
articleCard.readMore

as-a-junior-engineer

As a junior engineer, there’s simply no substitute for getting the first 100K lines of code under your belt. The “start over each day” method will help get you to those 100K lines faster.You might think covering the same ground multiple times isn’t as valuable as getting 100K diverse lines of code. I disagree. Solving the same problem repeatedly is actually really beneficial for retaining knowledge of patterns you figure out.You only need 5K perfect lines to see all the major patterns once. The other 95K lines are repetition to rewire your neurons. Algorithms we develop software by,很有同感的一段话,很多事情只有不断重复才能真正掌握它,例如走路,会走一次,不能算学会走路,只有不断地走,直到忽略你在走路这个事实之后,才算真正地学会了走路。

2025/2/15
articleCard.readMore

博客新计划

AI技术日新月异,能用AI做的事情越来越多。 作为一个普通人,知识和技能唾手可得,记忆性的东西不再重要,而独特的思维方式则是你区别于别人的重要标签。在这样的时代背景下,每个人越来越需要独立思考的能力,因此每个自己的独特想法、见解都值得被记录下来。 而作为一个blogger,也许在未来(或现在?),利用你的博客内容,AI可以重建你的思考方式,针对每一个新的事件,AI会给出你的评价,然后在跟自己真实的看法进行对照,是不是很有意思呢?。 基于上面的思考,我决定事无巨细地在这个博客中更新自己的技术内容,包括看到的技术内容引用,简单的comments,尝试新东西的过程,阅读技术代码的历程,造轮子的步骤,等等,总之就是不论大小,一概记录,相信当内容积攒越来越多后,基于这个博客的语料数据,结合我编写的代码,AI能够准确地重建一个我的程序员分身,这样未来也许我就不需要写代码了哈哈。

2025/2/15
articleCard.readMore

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 https://github.com/pytorch/pytorch/issues/122321 https://github.com/pytorch/pytorch/issues/122865 https://github.com/pytorch/pytorch/issues/99701 https://github.com/pytorch/pytorch/issues/117209

2025/1/29
articleCard.readMore

Python lru_cache 使用与源码解读

1. 用法说明 functools.cache和functools.lru_cache都是Python标准库functools模块提供的装饰器,用于缓存函数的计算结果,以提高函数的执行效率。 举一个简单的例子: 1 2 3 4 5 6 7 8 9 10 11 12 13 from functools import lru_cache import timeit @lru_cache def factorial(n): return n * factorial(n-1) if n else 1 execution_time1 = timeit.timeit("factorial(64)", globals=globals(), number=10000) execution_time2 = timeit.timeit("factorial.__wrapped__(64)", globals=globals(), number=10000) print(f"Execution time1: {execution_time1:.4f} seconds") print(f"Execution time2: {execution_time2:.4f} seconds") print(f"Speedup: {execution_time2/execution_time1:.4f} times") 其中__wrapped__ 表示装饰器中原始的函数,也就是没有作用装饰器之前的裸函数。 代码输出如下: 1 2 3 Execution time1: 0.0004 seconds Execution time2: 0.0016 seconds Speedup: 3.5078 times 可以看到,通过lru_cache保存factorial函数的中间结果,得到了3.5倍的加速。 通过这里例子,我们可以看到lru_cache的使用方式,也是比较简单: import lru_cache:: from functoools import lru_cache 给函数添加@lru_cache装饰器。 通过查看源码,可以看到lru_cache函数签名如下: 1 def lru_cache(maxsize=128, typed=False): 其中maxsize 参数表示缓存的最多结果数,默认是128。如果计算结果超过128,则遵循Least-recently-used (LRU)原则,将最近使用次数最少的缓存结果替换为当前的结果。如果设置maxsize=None,则缓存无上限,但内存占用也可能会增大,使用时多观察。 typed参数表示是否按类型缓存不同变量,即使数值一样。例如typed=True,那么f(decimal.Decimal("3.0")) 和 f(3.0)也会分开缓存。 2. 实际使用例子 上面只是一个玩具例子,实际代码中,lru_cache用法还是挺多的,这里举一些实际使用例子,来更清晰地理解它的功能。 2.1 get_available_devices 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 @lru_cache() def get_available_devices() -> FrozenSet[str]: """ Returns a frozenset of devices available for the current PyTorch installation. """ devices = {"cpu"} # `cpu` is always supported as a device in PyTorch if is_torch_cuda_available(): devices.add("cuda") if is_torch_mps_available(): devices.add("mps") if is_torch_xpu_available(): devices.add("xpu") if is_torch_npu_available(): devices.add("npu") if is_torch_mlu_available(): devices.add("mlu") if is_torch_musa_available(): devices.add("musa") return frozenset(devices) 代码地址: https://github.com/huggingface/transformers/blob/f11f57c92579aa311dbde5267bc0d8d6f2545f7b/src/transformers/utils/__init__.py#L298 这是获取所有可用 torch devices的代码,通过增加lru_cache进行缓存。 2.2 API请求缓存 1 2 3 4 5 6 7 8 9 10 11 12 import requests from functools import lru_cache @lru_cache(maxsize=32) def get_weather(city: str) -> dict: url = f"https://api.weather.com/{city}" response = requests.get(url) return response.json() # 多次调用相同城市时,直接从缓存读取 print(get_weather("beijing")) # 真实请求 print(get_weather("beijing")) # 命中缓存 2.3 读取配置 1 2 3 4 5 6 7 8 9 10 11 from functools import lru_cache import configparser @lru_cache(maxsize=1) # 只需缓存最新配置 def load_config(config_path: str) -> dict: config = configparser.ConfigParser() config.read(config_path) return {section: dict(config[section]) for section in config.sections()} # 多次读取同一配置文件时,直接返回缓存对象 config = load_config("app.ini") 2.4 包含参数的资源初始化 1 2 3 4 5 6 7 8 9 10 11 from functools import lru_cache import tensorflow as tf @lru_cache(maxsize=2) def load_model(model_name: str) -> tf.keras.Model: print(f"Loading {model_name}...") # 仅首次加载时打印 return tf.keras.models.load_model(f"models/{model_name}.h5") # 重复调用时直接返回已加载模型 model1 = load_model("resnet50") # 真实加载 model2 = load_model("resnet50") # 命中缓存 3. lru_cache源码分析 lru_cache源码在CPython源码目录的Lib/functools.py中,可以在GitHub上查看。 下面通过代码截图的方式详细分析源码。 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 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 def lru_cache(maxsize=128, typed=False): if isinstance(maxsize, int): # 如果maxsize为负数,则设置maxsize=0,也就是无缓存 if maxsize < 0: maxsize = 0 elif callable(maxsize) and isinstance(typed, bool): # maxsize没有传入,直接传入的是用户定义函数 user_function, maxsize = maxsize, 128 # 调用_lru_cache_wrapper创建wrapper,具体实现在底下 wrapper = _lru_cache_wrapper(user_function, maxsize, typed, _CacheInfo) wrapper.cache_parameters = lambda : {'maxsize': maxsize, 'typed': typed} # 调用update_wrapper来更新wrapper的元数据,使得与user_function一致 return update_wrapper(wrapper, user_function) elif maxsize is not None: raise TypeError( 'Expected first argument to be an integer, a callable, or None') def decorating_function(user_function): wrapper = _lru_cache_wrapper(user_function, maxsize, typed, _CacheInfo) wrapper.cache_parameters = lambda : {'maxsize': maxsize, 'typed': typed} return update_wrapper(wrapper, user_function) return decorating_function # LRU装饰器具体实现函数 def _lru_cache_wrapper(user_function, maxsize, typed, _CacheInfo): # Constants shared by all lru cache instances: # 每个object()得到的ID都是唯一的 sentinel = object() # unique object used to signal cache misses make_key = _make_key # build a key from the function arguments PREV, NEXT, KEY, RESULT = 0, 1, 2, 3 # names for the link fields cache = {} hits = misses = 0 full = False cache_get = cache.get # bound method to lookup a key or return None cache_len = cache.__len__ # get cache size without calling len() lock = RLock() # because linkedlist updates aren't threadsafe root = [] # root of the circular doubly linked list root[:] = [root, root, None, None] # initialize by pointing to self if maxsize == 0: def wrapper(*args, **kwds): # maxsize=0,说明无缓存,直接调用用户函数并返回结果 nonlocal misses misses += 1 result = user_function(*args, **kwds) return result elif maxsize is None: def wrapper(*args, **kwds): # 无限缓存情况,不用考虑LRU替换,直接匹配 nonlocal hits, misses # 生成包含args, kwds和typed的唯一的key key = make_key(args, kwds, typed) result = cache_get(key, sentinel) if result is not sentinel: # 找到了key,说明已经有缓存了 hits += 1 return result misses += 1 result = user_function(*args, **kwds) # 将本次结果进行缓存 cache[key] = result return result else: # 有缓存大小的情况,需要进行LRU替换 def wrapper(*args, **kwds): # Size limited caching that tracks accesses by recency nonlocal root, hits, misses, full key = make_key(args, kwds, typed) with lock: link = cache_get(key) if link is not None: # 使用双向链表结构体 # 在链表中删除命中的节点 link_prev, link_next, _key, result = link link_prev[NEXT] = link_next link_next[PREV] = link_prev # 将命中的节点移动到最后位置,root为最开始位置,表示最旧没用的数据,而last表示最新使用的数据 last = root[PREV] last[NEXT] = root[PREV] = link link[PREV] = last link[NEXT] = root hits += 1 return result misses += 1 result = user_function(*args, **kwds) #处理没命中的情况,因为如果命中的话,前面已经return了 with lock: if key in cache: # 这种情况说明别的线程写入了key,由于节点已经移动到最开始位置了,这里不需要做操作,只需要确保结果最后会返回 pass elif full: # 缓存已满,需要LRU替换 # 使用要删除的节点保存新插入的数据,避免额外的内存申请 oldroot = root oldroot[KEY] = key oldroot[RESULT] = result root = oldroot[NEXT] oldkey = root[KEY] oldresult = root[RESULT] root[KEY] = root[RESULT] = None # Now update the cache dictionary. del cache[oldkey] # Save the potentially reentrant cache[key] assignment # for last, after the root and links have been put in # a consistent state. cache[key] = oldroot else: # 没满的时候,直接插入link到最后 last = root[PREV] link = [last, root, key, result] last[NEXT] = root[PREV] = cache[key] = link # 检查缓存是否满了,使用cache_len而不是len(),因为len()可能会被lru_cache缓存,但属性不会,cache_len = cache.__len__ full = (cache_len() >= maxsize) return result def cache_info(): """Report cache statistics""" with lock: return _CacheInfo(hits, misses, maxsize, cache_len()) def cache_clear(): """Clear the cache and cache statistics""" # 清空cache nonlocal hits, misses, full with lock: cache.clear() root[:] = [root, root, None, None] hits = misses = 0 full = False wrapper.cache_info = cache_info wrapper.cache_clear = cache_clear return wrapper 4. lru_cache和cache的区别 functools.cache是Python 3.9引入的新特性,作为lru_cache的无缓存大小限制的一个alias。 具体来说,通过查看源码,可以发现cache是lru_cache的一个特例: 1 2 3 def cache(user_function, /): 'Simple lightweight unbounded cache. Sometimes called "memoize".' return lru_cache(maxsize=None)(user_function) 而lru_cache 的函数签名如下: 1 def lru_cache(maxsize=128, typed=False): 因此可以看出cache=lru_cache(maxsize=None, typed=False) 。因此cache函数有两个重要的特点: 缓存空间无限大,也就是说不存在缓存的字典的key值超过上限,需要替换掉那些最不常用的key的情况,可以保证所有函数都能命中,但代价是会占用更多的内存。 typed=False,表明不同类型的具有相同类型的数值会被当作一个值来缓存。 5. 不适合的应用场景 返回可变对象(如列表、字典)时,缓存的是对象引用,可能导致意外修改 函数有副作用(如写入文件、修改全局变量) 参数不可哈希(如传递字典、列表等可变类型) 参数组合可能性无限(导致缓存无限膨胀)

2025/1/29
articleCard.readMore