20230623esxi自动创建虚机+安装ubuntu

0.背景

做实验需要反复创建虚机装系统,略麻烦。打算给家里云做个低配版的虚机自动化管理

1.方案调研

esxi本身有接口和SDK可以给各种语言做自动化对接,从官方github就能找到不少资料

ubuntu自动安装也比较成熟,常见的方案都是使用cloud-init来提供user-data,可以参考官方文档试试

2.虚机自动创建

使用govc命令行即可,脚本参考https://github.com/vmware/govmomi/blob/main/scripts/vcsa/create-esxi-vm.sh  去掉了对我没用的部分并增加了几个参数方便自己使用,完整文档请参考https://github.com/vmware/govmomi/tree/main/govc#usage

3.ubuntu自动安装

官方的cloud-image.ova我测了好几次都都没法用govc正常传递-options参数,json里指定的user-data并没有生效,不知道为什么,因此先尝试半自动手动修改引导参数测试

3.1.user-data准备

首先按官方文档准备一个目录,放入user-data和user-meta后启动http服务器

user-data内容如下,有这么几点需要注意

  • 开头的第一行注释不要删
  • 在22.04.1这个版本的ubuntu下,如果不加refresh-installer会导致lvm的sizing-policy失效,40G系统盘默认只会用20G。
  • disable_suites如果不加security会导致本地实验老去联网升级安全补丁,为了加快速度我就关了,实际生产环境不要关
  • packages如果指定了内容,package_update无论是什么都会更新一次源

3.2.远程加载user-data

20.04.5的图形界面按F6和esc后可以编辑启动菜单的指令,改成这样就能从远程加载user-data了。

话说不知道为啥vmware粘贴多了会导致有概率内容丢失


22.04.1的那个图形界面没了,可以按c进入grub命令行,手动执行启动参数如下,注意nocloud-net后面的分号之前必须有反斜杠转义

vmware在这个命令行下的粘贴完全没问题

3.3.从网络启动并自定义内核启动参数

1.设置openwrt当DHCP server指定 tftp服务

https://openwrt.org/docs/guide-user/services/tftp.pxe-server

https://forum.openwrt.org/t/solved-dhcp-config-pxe-boot-from-external-tftp-server/5880

配置如下图

2.tftp由op、jumpbox、群晖负责(群晖的tftp测试失败,疑似是因为我的群晖不在测试网段,暂时使用op自带的tftp)

tftp内容制作如下,参考openwrt的文档修改而成。我使用的是BIOS legency引导,示例如下。

注意,根据https://askubuntu.com/questions/1235723/automated-20-04-server-installation-using-pxe-and-live-server-image中描述

使用cloud-config-url=/dev/null可以降低内存的使用要求

3.autoinstall的user-data和iso文件由jumpbox或omv负责提供

如上面参数所示临时用python启动了一个http服务器放在了http://192.168.2.159/iso/ubuntu-22.04.1-live-server-amd64.iso,后面考虑换成omv或群晖提供。理论上这里过了DHCP过程因此不受测试网段的限制才对

尝试使用cdrom挂载iso但使用pxe修改内核参数从光驱执行自动安装

https://www.cnblogs.com/xyshun/p/9427472.html

https://www.dddns.icu/posts/pxe/

https://github.com/pypxe/PyPXE

https://github.com/netbootxyz/netboot.xyz

4.全自动化

1.esxi自动化由govc搞定,创建后可拿到网卡mac地址

虚机创建见前面的脚本

从cdrom安装这种方式需要修改bios启动顺序才行,disk>net>cd-rom,disk放第一个是防止安装成功后还重复执行安装,net比cd-rom靠前是因为避免自动进光盘页面无法走自动化。

2.通过mac地址可控制openwrt的tftp按mac地址返回引导选项

自动获取mac如下

编写脚本按mac和hostname动态生成引导菜单并上传到tftp

注意文件名应该以01-开头,详见https://wiki.syslinux.org/wiki/index.php?title=PXELINUX

还可以用16进制的ip地址,总规则是

After attempting the file as specified in the DHCP or hardcoded options, PXELINUX will probe the following paths, prefixed with “pxelinux.cfg/“, under the initial Working Directory.

  • The client UUID, if provided by the PXE stack.
Note that some BIOSes do not have a valid UUID, and it might end up reporting something like all 1’s.
This value is represented in the standard UUID format using lowercase hexadecimal digits, e.g. “b8945908-d6a6-41a9-611d-74a6ab80b83d“.
  • The hardware type (using its ARP “htype” code) and address, all in lowercase hexadecimal with dash separators.
For example, for an Ethernet (i.e. ARP hardware type “1“) with address “88:99:AA:BB:CC:DD“, it would search for the filename “01-88-99-aa-bb-cc-dd“.
  • The client’s own IPv4 address in uppercase hexadecimal, followed by removing hex characters, one at a time, from the end. For example, “192.168.2.91” → “C0A8025B“.
The included program, “gethostip“, can be used to compute the hexadecimal IP address for any host.
  • Lowercase “default“.

3.引导选项中的iso暂时由群晖webstaion提供

静态站点,无需多言

暂时不需要了,ramdisk有问题。使用ramdisk加载iso后安装很慢,应该是我给的内存太少了。

4.引导选项中的autoinstall配置文件也由webstation提供

群晖自带的webstation可以用PHP,打算尝试使用PHP测试伪静态

使/prefix/{hostname}/user-data可按我指定的模板返回,并动态替换hostname

找到webstation的主nginxconfg文件,/var/tmp/nginx/app.d/server.webstation-vhost.conf,可以看到有include

按路径新建一个 /usr/local/etc/nginx/conf.d/blablablabla/user.conf

/usr/local/etc/nginx/conf.d/a9d1c5c8-082a-482c-8fe1-73afc670ff6c/user.conf

感谢eric提供的参考

编写index.php如下

 

参考

https://ubuntu.com/server/docs/install/autoinstall

https://github.com/hedzr/pxe-server-and-focal

https://hedzr.com/devops/linux/build-pxe-server-and-autoinstall-ubuntu-server

https://github.com/vmware/govmomi/blob/main/govc/README.md

https://blog.amrom.tk/2022/2022-03-03-esxi-cloud-init/

https://cloudinit.readthedocs.io/en/latest/topics/examples.html

https://nickhowell.uk/2020/05/01/Automating-Ubuntu2004-Images/

https://discourse.ubuntu.com/t/automated-server-installer-config-file-reference/16613/42

http://hmli.ustc.edu.cn/doc/linux/ubuntu-autoinstall/ubuntu-autoinstall.html

 

 

20230622mysql in k8s

0.背景

视频版 TODO 待录制

原计划是从手动到容器化再到云原生,每种阶段的搭建方式都来一次,但实际上容器化和云原生基本上没什么太大差距。因此跳过容器化阶段直接进入云原生的数据库搭建。

mysql属于有状态的应用,因此它的存储在一般的docker中需要特殊处理。有一种说法是没有必要在docker中搭建mysql,这个说法不完全对,在测试环境容器化可以带来很大的灵活性。实际上生产环境下上只要存储弄好了之后利用k8s的资源管理和调度能力是可以搭出合适的mysql集群的

其实前段时间已经给paraparty的群服务器搭过一次了,这里是总结下经验,详细记录请参考https://paraparty.feishu.cn/docx/FsBNdoDQAo4E8VxN0zZceTP8nsh

1.前期准备

在k8s中搭建mysql架构如图,来自官方的文档,直接照着搭即可。版本选择 8.0.33

MySQL、Kubernetes 通用术语. 例如Node可能代指一个Kubernetes Node或一个MySQL Node,Cluster可能代指一个MySQL InnoDB集群或Kubernetes集群,ReplicaSet是MySQL和Kubernetes都有的一个特性。

1.1.计算资源

  • k8s系统使用Controllers来管理Pod(在其中运行的容器化负载)的生命周期。Controllers是一个通用工具提供了广泛的服务能力,但复杂的服务需要包括operator之类额外的组件。Operator是一个运行在k8s集群中的软件和k8s-api进行交互来观测资源和服务来协助k8s进行生命周期的管理
  • MySQL Operator for Kubernetes 是一个专注于管理一个或多个由MySQL Server组成的MySQL InnoDB集群和MySQL Routers的工具。它自身在k8s集群中运行并作为k8s Deployment被管理,以此来保证自身的可用性 默认情况下它被部署在’mysql-operator’的k8s命名空间内,并监视k8s集群中所有的InnoDB集群和相关联的资源,为了执行这些任务operator订阅k8s-api服务来更新事件并按需连接到被管理的MySQL Server实例上。在k8s-controller的最顶层operator配置MySQL servers,使用Group Replication配置高可用集群,以及Mysql Router
  • MySQL InnoDB Cluster 一旦InnoDB Cluster (InnoDBCluster) 资源被部署到k8s-api上,Operator会创建如下的资源: k8s-StatefulSet来运行MySQL Server实例 他们管理Pods并分配相关的存储卷(storage Volume,sc),这个StatefulSet运行多个容器作为Pod,每个Pod都被其管理。 开始的几个提供一系列的初始化步骤来准备MySQL Server的配置和数据文件夹,紧接着两个容器作为业务容器保持存活。其中一个名为mysql的容器运行MySQL Server本体,另一个名为sidecar的容器来和operator写作负责节点的本地管理
  • k8s-Deployment来运行MySQL Routers MySQL Routers是无服务的状态应用,依照客户端要求来转发流量到当前的主节点或从节点,operator可以调整router的数量来根据实际的情况扩容或缩容

1.2.服务暴露

  • 一个MySQL InnoDB集群部署会创建如下的k8s-Services: 一个名为InnoDB Cluster的服务,它负责作为MySQL Router的主入口。同时提供了一个固定的本地DNS域名,格式为'{clustername}.svc.cluster.local’,并同时暴露需要的端口 详细信息查看官网文档 “MySQL InnoDB Cluster Service Explanation” and Connecting to MySQL InnoDB Cluster.
  • 第二个名为'{clustername}-instances’的服务为每个独立的实例提供了固定的本地DNS域名。一般来说他们不应该被直接访问,而应该使用主服务的域名来保证访问到正确的主节点或从节点。然而,为了运维或或监控来使用这个域名是有必要的,每个pod上都预装了MySQL Shell
  • Operator 创建和管理的其他资源不应该被手动修改,包括: 名为'{clustername}-initconf’的k8s-ConfigMap包括了MySQL 服务的配置信息,想要修改生成的my.cnf配置文件,查看 “Manifest Changes for InnoDBCluster”. 一系列k8s-Secrets用于存储系统不同部分的凭证, 名字是 ‘{clustername}.backup’, ‘{clustername.privsecrets}’, and ‘{clustername.router}’.
  • 获取operator创建的MySQL账号和关联的密码,查看 “MySQL Accounts Created by InnoDBCluster Deployment”.

2.mysql-operator

如架构图所示,mysql-operator负责具体pod的创建和维护,因此需要先安装mysql-operator,文档参考官方文档

2.1.原理追踪

简单来说mysql-operator是一个给mysqlsh开发的python模块,它会启动一个python脚本通过kopf监听k8s集群资源变动的事件并执行预定义的操作
详细追踪过程如下:
集群部署的mysql-operator会启动mysqlsh,利用–pym加载外部名为mysqloperator的python模块

这个模块在community-operator镜像制作的时候会被打进去,关键命令是COPY mysqloperator/ /usr/lib/mysqlsh/python-packages/mysqloperator

https://github.com/mysql/mysql-operator/blob/a711f0d08dc7097778c1417779146c8ce89721c4/docker-build/Dockerfile#L19
默认指定的参数是operator,根据mod = importlib.import_module(entrypoints[sys.argv[1]], "mysqloperator")可以定位到operator_main.py
https://github.com/mysql/mysql-operator/blob/a711f0d08dc7097778c1417779146c8ce89721c4/mysqloperator/__main__.py#L25
https://github.com/mysql/mysql-operator/blob/a711f0d08dc7097778c1417779146c8ce89721c4/mysqloperator/operator_main.py#L46
根据这个可以确定mysql-operator使用的是kpof来监听k8s集群事件的
kpof官方文档 https://kopf.readthedocs.io/en/stable/
kpof源码https://github.com/nolar/kopf
根据operator_xxx.py的命名风格猜测operator_cluster.py是我要找的内容,点开后可以明显看到@kopf.on.create 装饰器(py里叫decorator,java叫注解)
官方文档参考https://kopf.readthedocs.io/en/stable/handlers/#registering
https://github.com/mysql/mysql-operator/blob/a711f0d08dc7097778c1417779146c8ce89721c4/mysqloperator/controller/innodbcluster/operator_cluster.py#L52
往下翻翻很容易看到statefulset = cluster_objects.prepare_cluster_stateful_set(icspec, logger)
https://github.com/mysql/mysql-operator/blob/a711f0d08dc7097778c1417779146c8ce89721c4/mysqloperator/controller/innodbcluster/operator_cluster.py#L165
跳转到cluster_objects.py很容易发现yaml定义了
https://github.com/mysql/mysql-operator/blob/a711f0d08dc7097778c1417779146c8ce89721c4/mysqloperator/controller/innodbcluster/cluster_objects.py#L226
保险起见往下追一层,同理根据一样的原则很容易追到init对应的python代码是在init_main.py的main
parser.add_argument('--datadir', type = str, default = "/var/lib/mysql", help = "Path do data directory")
https://github.com/mysql/mysql-operator/blob/a711f0d08dc7097778c1417779146c8ce89721c4/mysqloperator/init_main.py#L77

2.2.mysqlsh的使用

文档 https://dev.mysql.com/doc/mysql-shell/8.0/en/mysql-shell-features.html
除了root的密码可以作为运维使用之外,localroot在sidecar中也可以使用,虽然官方不建议但是这个省事。其他账号参考https://dev.mysql.com/doc/mysql-operator/en/mysql-operator-mysql-accounts.html
localroot的sock放在了/var/run/mysqld/mysql.sock/var/run/mysqld/mysqlx.sock,通过以下方式可进入shell且无需密码
  • 执行mysqlsh 'localroot@(/var/run/mysqld/mysql.sock)'
  • mysqlsh下执行\connect localroot@(/var/run/mysqld/mysql.sock)
  • 其他语法可参考https://dev.mysql.com/doc/mysql-shell/8.0/en/mysql-shell-connection-socket.html

3.部署记录

https://dev.mysql.com/doc/mysql-operator/en/mysql-operator-innodbcluster-common.html

创建以下资源即可间接控制mysql-operator搭建集群

详细配置可以参考https://dev.mysql.com/doc/mysql-operator/en/mysql-operator-innodbcluster-simple-kubectl.html

假设namespace是mysql-test,集群名是cluster1的话,创建完成后会暴露如下内网域名

检查集群状态

集群创建后会有几个默认账号,一般建议用root作为超管使用。如前面步骤所示root是存在secret里的,其他的几个账户也是在相同的位置。mysqlbackup、mysqlrouter、mysqladmin这几个账户的密码都能从test1-backuptest1-routertest1-privsecrets中找到。其他账户请参考1.2中的链接

 

20230619离线导入导出镜像

 

20230610企业级web开发特性总结

0.前言

做了这么久web开发,先后接触过不少python、Java、C#、Js、Kotlin、Go等主流语言的web开发框架,但其实时间长了会发现企业级web开发的特性要求是固定的,各个语言和框架都有自己的方案但殊途同归。特此做个总结系列,相对系统的梳理和回忆下我的知识。以下特性不限于单一语言,从架构设计上考量的话,只要满足相关特性即可,这是语言无关的,只要能达成既定的技术目标就好。

因为其中每点都能拿出来单独展开,因此第一版暂时开坑写个提纲,然后看我心情慢慢填坑

1.接入层网络特性

DNS分地区多IP解析+健康检查+灾备自动切换
L4VIP-L4RS四层负载均衡+健康检查+灾备自动切换
L4RS-L7IPs七层负载均衡+健康检查+灾备自动切换
L7IPs-serviceIPs负载均衡+健康检查+灾备自动切换
L4VIP直通serviceIPs

2.DevOps特性

监听代码变动
容器化打包
平滑关机
健康检查
流量路由
多环境打包

3.应用能力特性

服务注册
服务发现

mesh RPC能力
日志框架
本地、远程配置文件读取
统一结果类建模
序列化框架
应用声明周期监听(启动/退出/重启事件)
请求统一拦截器Interceptor
统一异常处理返回值
国际化
请求追踪能力 ThreadLocal
通用RouterMapping,支持路径前缀前缀复用到Class注解上
MVC解析HTTP报文,queryString,pathVariable,jsonBody
本地内存或分布式EventBus
本地内存缓存
分布式消息队列
分布式内存缓存
切面拦截能力
方法异步调用
定时任务调度
单元测试能力
依赖注入能力
资源池化能力
mysql数据库集成
mongo数据库集成
多数据源、故障自动切换
读写分离
存储层ORM、OGM

20230515LLM笔记

0.前言

先叠个盾:我AI水平有限,哪写的不对欢迎大佬指正。

1.原理

LLM是根据上文预测下一个最大可能性的token,直到语句结束或超长。常见的对话只是表现形式的一种,实际运行的原理是对文本的补全预测。
关于部分人对于模型表现出的行为产生了偏离实际的想法,需要强调现在的模型还没有记忆,时刻记得无论输出什么内容,即使行为再像一个人,它也依旧是静态的
训练数据里是把对话作为整体的,因此当你输入的内容被拼接成训练数据里的前半部分,模型就会根据输入补全后面的内容,这部分内容再以回复的形式展现到聊天框。下一句你发送的内容的前面会被拼接上之前所有的历史记录输入到模型中进行下一次预测,表现出的记忆能力是这样做到的,并不是模型本身有记忆。

2.prompt方案

2.1.Input-output (IO) prompting

最原始的prompt方法,直接向模型输入prompt,并得到输出

2.2.embedding + 向量数据库

当输入的prompt超过模型长度限制后,一个方向是增加模型的context,比如GPT4能到32k,实测足够传递很长的信息给它了,但如果还不够
另一个方向就是缩减prompt的长度。
embedding可以使文本转为向量,利用向量数据库存储大量内容,在构造prompt时使用向量搜索相关信息动态拼接prompt,可以用来减少prompt长度完成大容量知识库的搜索
早在2018毕业的时候就有同学使用word2vec生成词向量并做文本的情感分析,原理类似。
但这里有问题需要注意,词向量本身有个维度是N,一个句子会被分为多个词,怎么把句子转成一个向量需要根据不同场景选择策略
例如CoSENT(Cosine Sentence) model可以做到将句子映射到768维的稠密向量
以下是GPT的回答
Based on the description you provided, the “CoSENT” or “Cosine Sentence” model appears to be a sentence encoder that maps sentences into a 768-dimensional dense vector space. This type of model is often used for tasks such as sentence embeddings, text matching, or semantic search.
Sentence embedding models like “CoSENT” aim to capture the semantic meaning of sentences and represent them as fixed-length vectors in a high-dimensional space. These vectors can then be used for various natural language processing tasks, including similarity comparison, clustering, or retrieval.
While I’m not familiar with the specific details and architecture of the “CoSENT” model, based on the functionality you described, it seems to fall under the category of language models. Language models, including both generative models like ChatGPT and encoder models like “CoSENT,” contribute to the broader field of natural language processing and understanding.

参考资料

https://github.com/facebookresearch/dinov2
常见的向量数据库 https://python.langchain.com/en/latest/modules/indexes/vectorstores.html

3.prompt技巧

3.1.通用技巧

清晰的描述问题,包括合理使用界符分隔数据和指令

3.2.few-shot prompting

在prompt中提供部分例子可以得到更好的效果
Language models are few-shot learners. NeurIPS.
https://proceedings.neurips.cc/paper/2020/file/1457c0d6bfcb4967418bfb8ac142f64a-Paper.pdf
https://zhuanlan.zhihu.com/p/200978538

3.2.CoT prompting

在prompt中说明请逐步思考并提供部分示例
英文let’s think step by step
Chain of Thought https://arxiv.org/pdf/2203.11171.pdf

Self-consistency with CoT (CoT-SC)

https://arxiv.org/pdf/2203.11171.pdf
https://zhuanlan.zhihu.com/p/609739922

采样方法

temperature sampling (Ackley et al., 1985; Ficler & Goldberg, 2017),
top-k sampling (Fanet al., 2018; Holtzman et al., 2018; Radford et al., 2019)
nucleus sampling (Holtzman et al.2020)
Sample-and-Rank
Beam Search
Ensemble-based Approaches
概率论概念:
边缘化 https://cloud.tencent.com/developer/article/1096441

Auto CoT

为了缓解手动设计的困难,Auto-CoT方法自动构建具有questions和reasons chains的演示。

3.3.USP 自适应prompt

一个通用的自适应prompt方法,突破了零样本学习的瓶颈

  • 谷歌提出了一种 Universal Self-adaptive Prompting (USP) 方法,对LLMs的零样本学习进行了优化,同时也适用于少样本学习任务。USP只需要少量未标记的数据,就能大幅提升LLMs在20多个自然语言理解和生成任务上的表现。实际上,结果比起少样本基线方法甚至更好!
  • 论文:Universal Self-adaptive Prompting

4.能力扩充

4.1.和外界交互的能力

Toolformer
https://arxiv.org/pdf/2302.04761.pdf
On the other hand, recent work has explored the use of pre-trained language models for planning and acting in interactive environments (Ahn et al., 2022; Nakano et al., 2021; Yao et al., 2020; Huang et al., 2022a),

4.2.支持AI自主制造工具

待收集资料+测试
https://arxiv.org/pdf/2305.17126.pdf

5.多次prompt组合调度

5.1.ToT 增加回溯、全局计划能力

https://arxiv.org/pdf/2305.10601.pdf
https://github.com/ysymyth/tree-of-thought-llm

3.2.ReAct范式

Thought Act Observation,让AI思考、行动、观察并重复这个过程
https://arxiv.org/abs/2210.03629

5.3.多步骤推理自动生成框架ART

《ART: Automatic multi-step reasoning and tool-use for large language models》
https://link.zhihu.com/?target=https%3A//arxiv.org/abs/2303.09014

6.服务层实现框架

6.1.LangChain增加调度逻辑、支持外部工具调用

https://github.com/hwchase17/langchain
https://liaokong.gitbook.io/llm-kai-fa-jiao-cheng/#vectorstores-xiang-liang-shu-ju-ku
https://python.langchain.com/en/latest/modules/indexes/vectorstores/examples/chroma.html#persistance
SayCan dataset (Ahn et al., 2022) 包含自然语言指令 到 机器人动作序列 的映射

参考

https://wqw547243068.github.io/prompt

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