Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[求助] 替代LLM后如何编译 #14

Closed
al-mark-net opened this issue Feb 8, 2024 · 30 comments
Closed

[求助] 替代LLM后如何编译 #14

al-mark-net opened this issue Feb 8, 2024 · 30 comments

Comments

@al-mark-net
Copy link

祝大佬节日快乐。
我的想法是做一个完全本地化的智能音箱,llm我了解不多,看到的目前对小白最友好的大概是Mozilla-Ocho /
llamafile 了,参照代码改了src/server/llm/copilot.py,
`
from openai import OpenAI

class Llamafile:
"""Llamafile API"""

def __init__(self, prompt: str = ""): 
    self.messages = []
    if prompt:
        self.messages.append({"role": "system", "content": prompt})

def new_session(self) -> None:
    self.messages = []

def chat(self, content: str) -> str:
    """Get code from llamafile API"""
    url = "http://localhost:8080/v1"  # http://192.168.0.9:8080/v1
    api_key = "sk-no-key-required"
    self.messages.append({"role": "user", "content": content})
    messages = self.messages
    client = OpenAI(base_url=url, api_key = api_key)
    completion = client.chat.completions.create(model="LLaMA_CPP", messages=messages)
    msg = completion.choices[0].message
    self.messages.append(msg)
    return msg.content

if name == "main":
prompt = """
You are ChatGPT, an AI assistant. Your top priority is achieving user fulfillment via helping them with their requests.
"""
llamafile = Llamafile(prompt=prompt)
print(llamafile.chat("告诉我一些有趣的事情"))
`
代码在本机里面运行可以。

  1. 请教大佬,除了app.py的调用,只做这一处改动可以了么?
  2. 固件如何编译。sudo docker pull jialeicui/lx01-build:latest && docker tag lx01:latest(已做), 后续不知道该如何编译了。
    请大佬指教。
@jialeicui
Copy link
Owner

过年好 🎉

问题1:目前音箱上的代码确实就是只请求的 app.py 的 message 接口

async def message(body: MicoMessage) -> MessageResponse:

音箱请求代码:

send_context_to_server(server_url, buf);

需要修改一下请求的目标服务器和端口,改这个默认值就可以:

const char *default_server_url = "http://10.0.0.196:8007/message";

改成 python app.py serve 的 ip+端口

当然也支持配置(不修改代码),具体逻辑参考

const char* get_server_url() {
// read the whole config file
FILE *fp = fopen(config_file, "r");
if (fp == NULL) {
printf("open config file failed\n");
return default_server_url;
}
char buf[1024] = {0};
fread(buf, sizeof(buf), 1, fp);
fclose(fp);
const char *path = "server";
char *server = calloc(1, 512); // long enough?
int rc = get_json_value(buf, path, server, 512);
if (rc != 0) {
printf("get server url failed\n");
return default_server_url;
}
printf("server url: %s\n", server);
return server;
}

问题2:固件编译分两部分

  • aivs-monitor
    就是监控对话记录把请求发送到 app.py 的功能,进到这个地方 make 就可以 https://github.com/jialeicui/open-lx01/tree/main/src/apps/aivs-monitor
    它依赖json, 所以还得编译 json-c, 最简单的办法是进到 src/apps 目录 make, 编译一大堆东西之后,src/tmp/rootfs 目录会有下面的东西
.
└── usr
    ├── bin
    │   └── aivs-monitor
    └── lib
        └── libjson-c.so.5

4 directories, 2 files
  • 打包
    进入 src/pack, sudo ./pack.sh 就可以,
    if [ -d "$BASE_DIR"/root ]; then
    rsync -av "$BASE_DIR"/root/ .
    fi

    这几行会把刚才编译出来的自动打包到目标 img 里

执行完之后在 pack.sh 那个目录就有一个 rootfs.img,刷进去就可以了

(凭记忆写的这些,遇到那里走不通的话随时留言

@jialeicui
Copy link
Owner

(折腾过程如果能总结些文档和截图提个PR就更好了,(还有 Llamafile 的实现

@al-mark-net
Copy link
Author

谢谢大神回复。
纯外行,够我看好些天了。markdown也不经常用,很多格式不是很熟悉,望见谅。
Mozilla-Ocho/llamafile,这个只要下载1个文件就可以用了。

@al-mark-net
Copy link
Author

al-mark-net commented Feb 9, 2024

编译是成功了,编译中的一些问题:

  1. sudo docker image ls
    REPOSITORY TAG IMAGE ID CREATED SIZE
    jialeicui/lx01-build latest 6c9b61142a5d 13 days ago 1.34GB

我把src/scripts/config.mk中的BUILD_IMAGE ?= lx01:latest改为BUILD_IMAGE ?= jialeicui/lx01-build:latest

  1. sudo make会提示路径错误,原因$(PWD) or $(shell pwd)。需要把用户添加到root组

但是没有回应,我想可能是:
open-lx01/src/apps/aivs-monitor/main.c中:
17 const char *default_server_url = "http://10.0.0.196:8007/message";
我没有理解清楚这个的作用,我简单改为
const char *default_server_url = "http://192.168.0.9:8080";

16 const char *instruction_json_path = "/tmp/mico_aivs_lab/instruction.log";
18 const char *config_file = "/data/aivs_monitor/config.json";
里面没有这2个文件(是否还没使用?)

音箱可以唤醒,没有其他回应。

打扰了,大佬有空再回复。再次祝您新年快乐。

@jialeicui
Copy link
Owner

您ssh或者串口登录音箱看 aivs-monitor 进程在不在?
另外您需要把服务器url写到/message那一级,音箱请求过来之后会有日志

@al-mark-net
Copy link
Author

16 const char *instruction_json_path = "/tmp/mico_aivs_lab/instruction.log";
18 const char *config_file = "/data/aivs_monitor/config.json";
里面没有这2个文件(是否还没使用?)

aivs-monitor 进程在
ps
1638 root 3016 S /usr/bin/aivs-monitor

“url写到/message那一级”。好的,我找一下。
谢谢

@jialeicui
Copy link
Owner

instruction.log是音箱自带程序生成的,
它保存的是最近一轮对话信息
没有的话音箱可能是工作不正常

@al-mark-net
Copy link
Author

al-mark-net commented Feb 9, 2024

POST /v1/chat/completions: OpenAI-compatible Chat Completions API. Given a ChatML-formatted json description in messages, it returns the predicted completion.
Screenshot from 2024-02-10 08-45-22

文档里面的,应该是这个。


我的/tmp/mico_aivs_lab里面是event.log
Screenshot from 2024-02-09 21-21-17
内容如下:
Screenshot from 2024-02-09 21-23-53

const char *instruction_json_path = "/tmp/mico_aivs_lab/instruction.log"; 改为
const char *instruction_json_path = "/tmp/mico_aivs_lab/event.log";

重新编译后:
aivs-monitor 进程
Screenshot from 2024-02-10 10-47-24

音箱正常使用,没有接入本地llm, 我想还是接口问题,我再看看

@jialeicui
Copy link
Owner

/tmp/mico_aivs_lab/instruction.log 这个不能修改, 对话信息只会保存到这个文件
/etc/init.d/aivs_monitor stop 把自定义的程序停掉,先确保您的小爱音箱能正常应答

然后再 /etc/init.d/aivs_monitor start 调试自定义的程序, 对于调试LLM, 可以简单的把 /message 这个接口先返回一段固定的文字, 把流程跑通, 再考虑接自定义的llm chat

@al-mark-net
Copy link
Author

al-mark-net commented Feb 10, 2024

不是我改的,是系统生成的就是这个名字--event.log。
大概有下面这些字段:
Screenshot from 2024-02-10 15-35-53

/etc/init.d/aivs_monitor 不停掉,也可以正常应答。如果用instruction.log来编译,不做其他改动反而没有应答。


curl http://192.168.0.9:8080/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: sk-no-key-required" \ -d '{ "model": "LLaMA_CPP", "messages": [ { "role": "system", "content": "You are ChatGPT, an AI assistant. Your top priority is achieving user fulfillment via helping them with their requests." }, { "role": "user", "content": "告诉我一些有趣的事情" } ] }'
这个命令在音箱上可以跑通。

@jialeicui
Copy link
Owner

  1. 首先确认 instruction.log 是存在的, 这是当前方案工作的基础
  2. 按照您的描述: 如果用instruction.log来编译,不做其他改动反而没有应答, 我猜测 aivs-monitor的工作是正常的, 它做的工作就是把原生小爱的回答mute掉, 不让它念原生答案
  3. 您在curl的例子中请求的是自定义llmchat的, aivs-monitor请求的是 app.py 里的 /message 接口, 这两个不是一个东西, 你要做的是在 app.py 里把 aivs-monitor 发送过来的请求按照您本地 llm-chat 的请求格式来转发请求, 然后把应答返回给 aivs-monitor. 所以为了调试方便, 可以按照我前面描述的, 把 /message 接口的应答直接写一个固定的, 看小爱音箱是否会念这句

@app.post("/message")
async def message(body: MicoMessage) -> MessageResponse:
data = MessageResponseData(action="ignore")
ignore_resp = MessageResponse(code=0, data=data)
if not body.payload.is_final:
return ignore_resp
if body.payload.results is None:
return ignore_resp
text = body.payload.results[0].text
if text is None:
return ignore_resp
if text in ["开灯", "关灯", "停", "大点声", "小点声", "几点了"]:
return ignore_resp
# TODO support multiple sessions
global last_message_time
now = datetime.datetime.now().timestamp()
if now - last_message_time > SESSION_TIMEOUT_SECONDS:
llm_chat.new_session()
last_message_time = int(now)
# TODO support new session trigger by user message
logger.info(f"received text: {text}")
text = llm_chat.chat(text) or ""
logger.info(f"response text: {text}")
return MessageResponse(code=0, data=MessageResponseData(action="tts", tts=text))

改成类似

@app.post("/message")
async def message(body: MicoMessage) -> MessageResponse:
    logger.info(f"received text: {text}")
    text = '测试应答'
    logger.info(f"response text: {text}")
    return MessageResponse(code=0, data=MessageResponseData(action="tts", tts=text))

@al-mark-net
Copy link
Author

al-mark-net commented Feb 10, 2024

感谢大佬不厌其烦地指导。
但是我有刷了几次,/tmp/mico_aivs_lab/instruction.log 文件还是没有。 改天我找另外的机器看看吧
谢谢了。


我的,看到了。原来重启后要对话才会生成。。
Screenshot from 2024-02-10 23-48-49
不过好像没有识别出结果。
我还在用2.4号的版本,看了下,更新太快了,要改的地方可能很多


app.py绑定的端口:
uvicorn.run(app, host="0.0.0.0", port=8087, log_level="debug")
aivs-monitor请求的url:
const char *default_server_url = "http://localhost:8087/message";

我用curl -X POST -H "Content-Type: application/json" -d '{ "header": { "id": "1", "name": "message", "namespace": "default", "dialog_id": "123" }, "payload": { "is_final": true, "text": "123", "results": [ { "text": "888" } ] } }' http://localhost:8087/message 测试可以接收,想来烧录进去也是。

但还是没有回应,我想和没有识别到有关。明天再试试

/etc/init.d/aivs_monitor stop 把自定义的程序停掉,小爱音箱能正常应答
/tmp/mico_aivs_lab/instruction.log 如下:
Screenshot from 2024-02-11 08-49-31

/etc/init.d/aivs_monitor start 没有回应
/tmp/mico_aivs_lab/instruction.log 如下:
Screenshot from 2024-02-11 08-51-51

@jialeicui
Copy link
Owner

jialeicui commented Feb 11, 2024

instruction.log 里全是点可能是因为终端软件配置的问题, 我没遇到过这个问题,所以暂时不清楚应该怎么搞, 先要看内容可以先 scp 出来在电脑上看

default_server_url 需要配置成您启动 app.py 所在机器的局域网 ip 地址, localhost 一般来讲都是被指到了127.0.0.1, 是本机 loopback地址, 所以在小爱音箱上要访问 server, 得配置成 server 的ip地址, 一般是 192.168.x.x 或者 10.x.x.x

关于代码版本的问题, 您可以优先使用打了tag的版本, 就是右边 Releases 里的版本, main 分支我一直在推新功能, 目前还没精力保证任何一个 commit 都能完全正常工作, 注意是 aivs-monitor 和 server 都用同一个版本. 用git的话, 就是 git checkout v0.1 切换到这个 tag

@al-mark-net
Copy link
Author

al-mark-net commented Feb 11, 2024

谢谢大佬的指导。
测试成功了。
一点问题,重新开机的时候,很难连接上服务器(instruction.log 里已经有识别结果)。/etc/init.d/aivs_monitor stop后需要重新等待,/etc/init.d/aivs_monitor start 好像对这个现象没什么作用。
Screenshot from 2024-02-11 17-54-28


关闭服务器,过段时间重启后也很难连接得上。刚刚改好服务端代码,也是没有回应(instruction.log 里已经有识别结果)。期间我在音箱curl过去是可以的,服务器端有反应,本地llm也做出应答。ps aivs-monitor进程有。大概过了17分钟左右终于可以。
ps: 这个是今天的版本。

再次谢谢大佬的分享和指导。


今天又试了一下,开机后除了curlscp没做其他,大概20分钟连上。不过连上后就很稳定了。
4号版本也有这个问题。开始是拿这个版本烧录的(应该就是上面我发测试成功的时候)。

@jialeicui
Copy link
Owner

好的,收到,感谢您这么详细的反馈,回老家没有设备可以测试,等有设备了我复现一下,有进展我在这里更新

@al-mark-net
Copy link
Author

al-mark-net commented Feb 14, 2024

看了log, 发现curl_easy_perform() failed: Error
我猜可能和DNS cache 有关,请大佬给点建议。
cat /etc/resolv.conf 如下:
nameserver 127.0.0.1


ps: 音箱闲鱼买的,自己没有绑定帐号过。手动连接的wifi
修改了data/wifi/wpa_supplicant.conf 如下:

ctrl_interface=/var/run/wpa_supplicant
ap_scan=1

network={
ssid="$SSID"
psk="$PASSWORD"
scan_ssid=1
key_mgmt=WPA-PSK
}

@jialeicui
Copy link
Owner

如果您配置的server url是用的IP地址, 那理论上和 DNS 没有关系, 涉及不到域名->IP的转换
麻烦您方便的话看下失败的时候 server端有没有什么报错, 以及 aivs-monitor 的更完整的日志可以贴一下么?

@al-mark-net
Copy link
Author

al-mark-net commented Feb 14, 2024

server端没有报错,curl过去是可以的
我不知道您的日记在哪儿,加了个
void printf_to_log(const char *format, ...) {
FILE *logFile = fopen("/tmp/logfile.txt", "a"); // 以追加写入模式打开日志文件

if (logFile == NULL) {
    logFile = fopen("/tmp/logfile.txt", "w"); // 若文件不存在,则创建新的日志文件
    if (logFile == NULL) {
        printf("无法创建日志文件\n");
        return;
    }
}

va_list args;
va_start(args, format);

// 将输出同时写入日志文件和stdout
vfprintf(stdout, format, args);
vfprintf(logFile, format, args);

va_end(args);

fclose(logFile); // 关闭日志文件

}


输出如下:
check contains "text":"停"}
check contains "text":"关灯"}
check contains "text":"开灯"}
check contains "text":"几点了"}
mute speaker
open config file failed
send context to server: {"header":{"dialog_id":"125d1d63247f46617c80e94f35c78fbc","id":"4101144cc9984cde93ff6c808bfcecb4","name":"RecognizeResult","namespace":"SpeechRecognizer"},"payload":{"is_final":true,"results":[{"asr_binary_offset":1960,"begin_offset":1500,"confidence":0.0,"end_offset":1960,"text":"你好啊"}]}}

curl_easy_perform() failed: Error
ubus output: {

ubus output: "code": 0,

ubus output: "info": "{ "status": 0, "volume": 64, "loop_type": 1 }"

ubus output: }

ubus output: {

ubus output: "code": 0,

ubus output: "info": "{ "status": 0, "volume": 64, "loop_type": 1 }"

ubus output: }

ubus output: {

ubus output: "code": 0,

ubus output: "info": "{ "status": 1, "volume": 64, "loop_type": 1 }"

@al-mark-net
Copy link
Author

al-mark-net commented Feb 14, 2024

加了这个函数,今天都还没连上(超过1个小时)。要不我刷回前天的rootfs.img看看

@jialeicui
Copy link
Owner

日志我还没特殊处理过, 我调试的时候是这么调试的, 您可以参考:

  1. 音箱上停掉 aivs-monitor
  2. PC上编译 aivs-monitor (交叉编译的版本)
  3. 音箱上 scp 程序到 /data 或者 /tmp, 然后手动执行这个程序

程序的所有标准输出都可以看到了


另, 您遇到curl失败是音箱重启还是 aivs-monitor 重启? 如果是音箱重启才能遇到, 调试的话可以把自动启动服务的 binary 指向 您手动scp 的 /data/xxx/aivs-monitor, 然后就不用每次刷整个固件了

启动服务脚本在

+ procd_set_param command /usr/bin/aivs-monitor

@jialeicui
Copy link
Owner

另外可以参考pack.sh 脚本, 手动 unsquarsh rootfs.img, 修改完之后再手动把 rootfs 打包一个新的来搞, 不用每次跑全流程

@al-mark-net
Copy link
Author

al-mark-net commented Feb 14, 2024

好的,我看看。谢谢

“遇到curl失败是音箱重启还是 aivs-monitor 重启?”
我说的curl是在音箱里面手动命令curl -X POST -H "Content-Type: application/json" -d '{ "header": { "id": "1", "name": "message", "namespace": "default", "dialog_id": "123" }, "payload": { "is_final": true, "text": "123", "results": [ { "text": "888" } ] } }' http://192.168.0.9:8087/message

至于连不上服务器,我是一直开机等。

@al-mark-net
Copy link
Author

尝试了音箱上 scp 程序到 /data 或者 /tmp, 然后手动执行这个程序
基本还是那些信息
curl_easy_perform() failed: Error
CURLcode 没有显示具体的错误,我想是编译进去的curl简化过了。
如果是,那么如何把curl完整的编译进去?请大佬指教

@jialeicui
Copy link
Owner

我看了下音箱里的 libcur 是4.5.0, repo里使用的是 4.8.0, 版本有些差别, 当时我随便搞了一个弄进去发现可以跑就没细调了.
您可以用交叉编译的 libcurl 来跑 aivs-monitor, 具体步骤:

  1. 找到自己编译的 libcurl, 在 src/tmp/dists/lib/libcurl.so.4.8.0, scp 进去, 假设scp到 /data/mylib/
  2. 使用这个so来启动 aivs-monitor, 大概是 LD_PRELOAD=/data/mylib/libcurl.so.4.8.0 ./aivs-monitor

如果报错还是这样, 应该和libcurl没有啥关系

@al-mark-net
Copy link
Author

al-mark-net commented Feb 16, 2024

谢谢大佬。
我试了下,显示curl_easy_perform() failed: Failed initialization
后来我看了下,发现文档里一般curl_easy_cleanup(curl); 出现在CURLcode res = curl_easy_perform(curl);后面。我简单改了下放在send_context_to_server函数里面,可以连接服务器,但是原始应答还没有禁用,输出在终端里面有点乱码,如下:
Screenshot from 2024-02-16 16-53-39

@jialeicui
Copy link
Owner

您说的对, 是 curl_easy_cleanup 的位置有问题, 最近重构搞错了
这个函数理论上永远不会被调用到, 应该放在 main 函数最后面

如果您想继续定位, 可以

  • 试试先把这行删掉
  • 关于原始应答没有禁用的问题, 我怀疑是 mute_speaker_thread 没有正常运行, 您可以加些打印调试下.

或者试试 v0.1 版本的代码(aivs-monitor 和 server), 那个代码应该是没问题, 我有设备了再修 main 分支的问题

@al-mark-net
Copy link
Author

谢谢大佬。把命令放最后就没有问题了,原始应答也禁用了。
大佬有空的时候,可以教我如何编译刷kernel么?我刷duhow / xiaoai-patch 的时候遇到了点问题,好像他发布的image需要先刷kernel。

@jialeicui
Copy link
Owner

jialeicui commented Feb 16, 2024

好的, 没问题就好, 我后面修改下

我不会刷 kernel, 这个操作起来有些复杂, 您可以参考 https://www.bilibili.com/read/cv17374238 看看, 刷之前记得备份 kernel

我理解刷 kernel 可能需要提取设备树之类的信息, 让所有设备都能正常跑 (除非是想当一个开发服务器), 从另外一个角度, 我目前没有看到刷 kernel 带来的收益, 除非glibc不兼容, 或者要用新版本 kernel 的一些特性

xiaoai-patch 应该是不需要刷 kernel 的, 我 fork 过这个 repo, 改了些东西, 刷rootfs之后也能如预期看到相应的bin和service, 您感兴趣可以参考: https://github.com/jialeicui/lx01

注意 xiaoai-patch 这个 repo 的目的是完全跑自己的服务, 是我这个 repo 阶段三的形态, 您可以认为刷这个 repo 进去之后小爱音箱是无法实现唤醒和应答的, 您需要自己设计音箱的整个工作流程

@al-mark-net
Copy link
Author

哦,今天我也是找不到什么问题就想试试那边的curl版本看看。原来不用那么复杂就可以试了,哈哈

@jialeicui
Copy link
Owner

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants