20230429 IaC之IDE,也许是下一个ChatIDE?

0.背景

继基于插件做到代码部分语义可视化、以及反向控制IDE浏览代码跳转之后,我想尝试进一步获得IDE的控制权,来完成其他想法中的数据收集和增强自动化办公的程度,小小的推进一下IaC的进程,顺带着给AI自动化控制IDE开启一个新的道路,给AI“看”和“动”的基础能力。

这个想法启发于黄老师在Archguard中使用的createRepl方法,可以在自己的应用程序中内嵌一个jupyter的后端来完成自定义DSL的REPL(参考 https://github.com/archguard/archguard/blob/master/architecture-as-code/repl-api/src/main/kotlin/org/archguard/aaac/repl/compiler/KotlinReplWrapper.kt )。要是模仿他的方法在插件中启动一个embedded-jupyter-repl后端,能否通过repl来越过jupyter来执行插件中的方法呢?

1.需求分析

实际上IDEA本身也是一个java进程,有着自己的JVM和类加载器,其实相同的功能还在官方的IDE Scripting Console中有所体现、还有名为liveplugin的IDEAplugin,都可以做到动态在已经运行了的IDE的JVM进程中执行额外的代码

众所周知Java的运行是需要先编译成字节码再加载到JVM中的,那么也就意味着如果没有字节码增强和动态类加载技术,仅凭借正常的Java反射是很难做到的。理论上大部分程序在启动之后其行为就已经固化,不太可能轻易的通过外部进行干预。因此jupyter-repl在正常情况下是没法控制它的父进程的才对。但createRepl方法中的embed参数又让我起了怀疑,特此继续调研之前设想的IDE中REPL的可行性。

实现这个功能的思路上大致可以分为暴力穷举方案、RPC方案、字节码增强&动态类加载方案、移花接木方案

暴力穷举:人工或通过某种形式提取IDEA目前已知所有公开的API,生成delegate代码写入一个插件中,通过RPC等形式暴露给外部使用,这个方法难度最低但工作量最大,并且随着IDEA自身的升级需要持续维护

RPC方案:相比暴力穷举做一层抽象,使用反射进行动态调用再暴露结果给外部,这个方案难度比前一个高,工作量少了很多。潜在的坑是数据类型不太好处理,容易出运行时的问题

字节码增强&动态类加载方案:使用asm等字节码增强技术实时修改JVM运行时的字节码来达到,这个方案难度继续提高,工作量相比前一个差不多。潜在的坑是字节码增强技术配合任意代码使用,不异于重写一个编译器,可能会重新发明一次JShell和部分的javac。

因为我这么懒必然不会优先考虑前面的,因此一直在关注有什么已经做的差不多的功能,稍加拼接就能复用的方案。

移花接木方案1:因为按之前的思路是想继续使用Java的生态直接复用,特意看了下java在9之后出的JShell,理论上这个能作为一个后端使用,让这个后端和已有的JVM共享类加载器,麻烦的是需要自己解决类加载器的问题

移花接木方案2:IDE自带的部分功能和历史上有人实现的插件做到了类似的能力,参考他们的原理和代码稍加修改也许能和jupyter结合起来。

2.前端方案

jupyter、shell //any way,只要方便交互就好,按历史经验和套路,只要能用shell或文本交互的最后都比较容易再做二次开发
https://github.com/jupyterlab/jupyter-ai
https://github.com/Kotlin/kotlin-jupyter
https://github.com/dev-assistant/skykoma-plugin-idea/blob/bundle-scripting-console/src/main/kotlin/cn/hylstudio/skykoma/plugin/idea/KotlinJsr223JvmScriptEngine4Idea.kt
https://github.com/unit-mesh/auto-dev

3.执行引擎方案

3.1.embedded kotlin compiler //demo测试成功

实际的依赖是kotlin-scripting-compiler-embeddable

参考IDE Scripting Console的实现方式,兼容JSR-223的脚本引擎都可以调用IDE自身公开的接口,通过追踪IDEA源码可以看到入口,参考附录。

可以从源码看到IDEA主平台只声明了接口,由各自的插件自行实现Factory接口,根据追踪可以看到名为KotlinJsr223StandardScriptEngineFactory4Idea的类,这里已经处理好了IDEA主JVM的类加载器,因此模仿IDEA的核心代码调用即可。

官方的功能相对比较简单且不是核心功能,只能选取部分代码传给script engine执行动作。因和不同的实现相关,控制台并不都会显示输出。因此相关的资料也及其稀少,通过阅读大量源码和官方的bug反馈,对比git log、release note等方式才能找到一点有用的信息作为参考,另外还需要防备官方后续废弃这个功能。

注意:在某些版本上无法使用这个功能,需要开启internal actions菜单才能看到入口,但我打算后续拆出来放插件里通过别的形式触发。
PS: 冷门知识还是得自己动手啊,chatgpt一直在胡编一些不存在的路径,指望AI偷懒只会浪费时间。这部分代码开始在主仓库后来分到了kotlin的仓库,然后又从kotlin拆走回到主仓库。
PPS: 在没有编译环境的情况下,追这么大型的源代码还是需要一些技巧的,这个等后面有闲心再写

测试脚本

环境测试

3.2.kotlin-repl //待详细调研

从kotlin-repl的启动命令可以分析原理,理论上也需要解决类加载器的问题

3.3.jshell

jshell跨越宿主jvm执行动态字节码理论上不可行,推迟

3.4.jupyter 集成 //成功

REPL除了动态执行代码以外,还要包括上下文记录、UI渲染等功能。上下文记录也就是前面定义的变量后面可以继续引用。根据之前测试JSR-223的Java默认实现来看效果不是很好,根本做不到历史的记录。
根据script engine成功的现象debug可以发现,是kotlin的插件自己实现了历史记录的记录,利用这个也许可以和jupyter做对接
那么jupyter-kotlin 的api能否复用呢?理论上可行,尝试安装jupyterlab如下

记录下当前的依赖版本

 

安装完jupyterlab和kotlin-kernel后可以在python包的路径中看到相关的文件夹,kernel的描述文件主要入口是kernel.json,内容如下

其中核心内容是argv,可以看到当kernel启动时候实际执行的是run_kotlin_kernel,其中connection_file每次动态生成,内容如下

文件中描述了本次内核启动需要的端口、标识、协议等信息传递给python脚本。python脚本主要用于拼接启动命令

主要是获取环境变量、寻找java可执行文件路径、获取当前文件夹等信息,拼接的命令如下。

修改run_kernel.py如下

目前add_kernel支持设置环境变量之类的参数,理论上可以把这个过程从原流程中剥离出来独立出一个jar来执行,这样可以不破坏原有的python文件同时完成自定义kernel的接入,目前仅是demo直接暴力修改了python脚本不是很优雅

0.0.9初步实现了用自动注册kernel,可手动指定python的目录,支持动态修改kernel.json。

剥离对官方run_kernel.py的依赖,暂时使用shell替换整个文件

20250201更新:更换为独立模块摆脱对官方的依赖 https://github.com/956237586/run_kotlin_kernel_idea

尝试手动指定、远程分配监听端口:根据调用栈可追踪,需要修改逻辑

https://github.com/jupyter-server/jupyter_server

https://github.com/jupyter-server/jupyter_client

fork官方库后修改对应的代码强制使用环境变量中的端口,打tag v8.4.3后可从github安装指定版本

之后可支持在kernel.json的环境变量指定端口

继续分析脚本逻辑,为了方便理解简化启动命令为  java -jar xxx.jar -classpath=xxx xxx.json -home=xxx -debugPort=xxx,可以看到入口是kotlin-jupyter-kernel-0.11.0-385.jar,这个jar是kotlin编写的

根据日志和源码的情况,如果我们想模拟这个过程,只需要让server在idea插件的上下文启动。

因为端口描述的文件每次都是动态生成的,因此修改run_kernel.py把命令行参数传给idea插件,idea插件再启动replserver

在idea插件里启动一个httpserver监听请求,接读取原来的参数调用embedKernel,同时替换classpath为当前线程上下文的classpath,示例请求如下

这样启动之后就可以尝试jupyter中执行kotlin了,遇到的错误如下

避开之前测试引入的库影响同时保证编译通过,仅保留相关的依赖

再次继续测试错误如下

按独立demo调整后错误消失,但我忘了为啥了,下一个错误是构造器初始化失败提示类型不匹配,还去github开了issue问

通过反复和demo对比,发现classLoader的继承关系不一样,虽然是相同的类但被不通的类加载器加载后实际是无法通用的

构造测试脚本

最后根据调整ide scripting console时候的经验,修改最初启动server入口独立Thread的classLoader为idea加载插件所用的classLoader即可解决问题

效果如下

在java11和下面的环境组合下测试,代码分支https://github.com/dev-assistant/skykoma-plugin-idea/tree/test-jupyter-java11

Unable to instantiate class Line_4_jupyter

目前在java17和下面环境组合下测试成功,代码分支https://github.com/dev-assistant/skykoma-plugin-idea/tree/test-jupyter-java17

 

3.5.livePlugin的实现方案//待调研

TODO

附录

JSR-223

https://www.jcp.org/en/jsr/detail?id=223

livePlugin

https://plugins.jetbrains.com/plugin/7282-liveplugin

IDE Scripting Console 功能文档
https://www.jetbrains.com/help/idea/ide-scripting-console.html
IDE Scripting Console 源码
https://github.com/JetBrains/intellij-community/blob/fc580582f211e5f8866462d8c596aaf8e36760ac/platform/lang-impl/src/com/intellij/ide/script/RunIdeConsoleAction.java#L88
内置的Kotlin脚本引擎 源码
https://github.com/JetBrains/intellij-community/blob/master/plugins/kotlin/repl/src/org/jetbrains/kotlin/jsr223/KotlinJsr223StandardScriptEngineFactory4Idea.kt
官方issues
https://youtrack.jetbrains.com/issues?q=%22IDE%20Scripting%20Console%22%20project:%20%7BIntelliJ%20IDEA%7D
https://visualstudio.microsoft.com/zh-hans/visual-cpp-build-tools/
https://mirrors.tuna.tsinghua.edu.cn/help/pypi/

https://saturncloud.io/blog/how-to-get-autocomplete-in-jupyter-notebook-without-using-tab/

https://browse.arxiv.org/pdf/2309.12499.pdf

20230423大模型上手笔记

背景

AI最近又开始火了尝试上手玩玩,我水平还停留在MNIST入门阶段,哪写的不对欢迎大佬指正。

基本概念

模型是一个或一组文件,里面存储了权值的集合,理论上就是一堆浮点数的集合。

模型的生成过程叫做训练,执行过程叫做推理。训练过程中和训练好后的模型可以持久化到硬盘中,结合结构信息可以反向序列化加载到硬件中。

默认的浮点数精度一般是32bit浮点(32fp,32bit,4byte),  半精度.half()就是16bit(16fp,16bit,2byte)

量化说的是把浮点数据继续缩减空间,4bit定点(int4,0.5byte),8bit定点(int8,1byte)

文本是可以转换成高维向量表示的,向量的每个维度都可以用浮点数表达,transformer模型计算(推理)的过程实际是对输入的向量进行补全。

和AI对话只是表现形式,本质上是根据上文猜测下文在概率上最大可能性的结果并进行补全,重复进行这个过程直到长度上限或终止符,然后再以对话形式展现。

huggingface提供了大量模型和数据可供下载,常见的模型是pytorch生成的,格式上大多是一个或多个bin文件,加上一个index.json。

至于模型是怎么训练的暂时还不是很清楚,这种格式能否以更可视化的方式获取信息也不知道。(有没有一种方式能自动计算当前硬件资源,给出合理的模型加载方式呢?)

huggingface同时提供了常用的python库可以方便的自定义模型结构、加载预训练的模型,利用accelerate库可以把模型拆开加载到不同的设备。

nvidia-smi可以看到显卡的显存占用,使用watch -n 1 -c nvidia-smi可以实时监控显存。

工程结构

目前见到的几个模型的项目工程结构从上到下一般为

web-ui(可选):自己手写或者从其他地方抄,常见的是gradio,server_name可以指定监听IP(和其他的软件一样,设置成127.0.0.1仅限本地访问,设置成0.0.0.0监听所有网卡地址), server_port可以指定监听端口,一般不要开启share=True否则会发布到公网

示例代码demo: 一般从github下,里面经常包含一个model(s)的目录

模型本身+模型配置(可选) : 一般从huggingface下,内容会和github有部分重叠。我暂时粗暴的把模型以外的东西复制到model(s)下一份方便python中引用和修改,模型本体存别处

conda虚拟环境:使用conda可以做到和venv、nvm类似的方式管理虚拟环境并相互隔离,包含pytorch底层和其他的python依赖

模型加载技巧记录

huggingface提供了众多抽象好的预训练模型结构,可以通过mixin等方式复用

from_pretrained是个使用python动态实现的模型加载器,内部会动态实例化预训练的模型,直接追踪我没找到链路,但可以通过type(model)的方式获取运行时类名从而debug到真实的模型实现类

from_pretrained参数支持设置load_in_8bit=True,可以在加载时候进行量化。

这个方法底层实际调用的还是pytorch,pytorch中加载模型可以指定map_location。

加载模型的行为默认是加载到内存,然后再转移到显存中。

如果模型太大无法完全加载到内存会报错,根据报错信息可以定位到/conda/envs/py38/lib/python3.8/site-packages/transformers/modeling_utils.py文件,把map_location=”cpu” 全部替换为map_location=”cuda” 就能直接加载到显卡而不经过内存

 

如果正常方式无法加载模型,可以借助accelerate库分开加载一个模型的不同部分到不同的设备

主要的变更是用load_checkpoint_and_dispatch替代from_pretrained,关键参数如下

device_map参数可以指定加载策略,  auto可以自动分割模型来最大化利用硬件,infer_auto_device_map也可以生成device_map

也可以传入dict结构指定每个部分使用什么设备加载cpu是使用RAM加载 disk 是使用磁盘加载,数字编号是显卡的编号

打印model.hf_device_map可以看到自动生成的device_map结构

max_momory可以限制每个设备初始加载最多可用的资源,dict的key定义同device_map的value,例如:

注意这个参数只是设置加载时候的限制,实际推理过程还需要显存,因此使用此种方式需要注意给显卡预留足够的空间用于推理,剩下的部分用CPU RAM凑。

(做个梦:啥时候显存价格能降低到和硬盘一样啊)

如果device_map中出现了disk,则需要指定offload_folder来指定目录,我一般用f”{model_path}/tmp”

记录

chatglm-6b 成功,但有点弱智还需要调教

stablelm-7b 成功,比chatglm强点有限

codegen-6b 16b 失败,加载不了,推迟

MOSS-plugin 成功,看文档是基于codegen的,理论上代码能力应该还可以才对。但单张显卡无法直接加载,利用cpu分段加载后速度太慢,等待量化模型再测试。

参考

https://gradio.app/docs/

https://github.com/IST-DASLab/gptq

https://github.com/qwopqwop200/GPTQ-for-LLaMa

https://github.com/openai/triton

https://github.com/TimDettmers/bitsandbytes

https://github.com/OpenLMLab/MOSS/issues/35

https://huggingface.co/docs/transformers/main/en/autoclass_tutorial

https://www.zhihu.com/question/362131975/answer/2182682685

https://arxiv.org/pdf/1607.04683.pdf

https://zhuanlan.zhihu.com/p/38328685

https://blog.csdn.net/lai_cheng/article/details/118961420

https://huggingface.co/docs/optimum/concept_guides/quantization

https://huggingface.co/docs/transformers/main/en/main_classes/quantization

https://pytorch.org/docs/stable/quantization.html

https://deepspeed.readthedocs.io/en/latest/memory.html

https://pub.towardsai.net/run-very-large-language-models-on-your-computer-390dd33838bb

https://huggingface.co/docs/accelerate/v0.18.0/en/package_reference/big_modeling

https://huggingface.co/docs/accelerate/v0.18.0/en/usage_guides/big_modeling#designing-a-device-map

https://huggingface.co/docs/accelerate/v0.18.0/en/usage_guides/big_modeling#loading-weights

https://github.com/OpenLMLab/MOSS/issues/38

20230407kubekey搭建k8s

视频版

https://www.bilibili.com/video/BV1gL411Y75L

 

背景

20230204尝试搭建k8s青春版

上次尝试了k3s,本来计划是尝试原版k8s,但看了下文档感觉略麻烦,我决定放弃。。。。

按上次eric的建议这次尝试玩下kubekey

安装准备

环境要求

Ubuntu 16.04,18.04,20.04CPU:2 核,内存:4 G,硬盘:40 G

机器节点2c4g * 3

  • 所有节点必须都能通过 SSH 访问。
  • 所有节点时间同步。
  • 所有节点都应使用 sudo/curl/openssl/tar
域名分配
k8s1.k8s.local
k8s2.k8s.local
k8s3.k8s.local
kubesphere.k8s.local

生成config

修改config

每个节点上安装依赖项

任务机上执行这个安装集群

安装过程

这时候可以进到命令行查看安装过程,ks-installer-86ddb55c5b-gwlhh就是安装的pod

可以进入pod容器查看目录

也可以直接用logs查看日志

观察到这两个pod一直起不来

都是源于configmap kubesphere-config不存在

多等了一会发现第二个pod ks-controller-manager-8569fb495c-b9qwv自己成功了,可能有降级?(来自20230701的更新:只是单纯的慢,实际是monitor还没装完,所以不动它也能自己好)

尝试手动修改kubesphere-config 里的jwtToken

kubectl get cm kubesphere-config -n kubesphere-system -o yaml

删除pod ks-apiserver后不再报错

参考

https://kubesphere.io/zh/docs/v3.3/installing-on-linux/introduction/multioverview/

https://github.com/kubesphere/kubesphere/issues/4729

http://www.minio.org.cn/docs/minio/kubernetes/upstream/

https://docs.ceph.com/en/quincy/cephfs/index.html