qinfengge

qinfengge

醉后不知天在水,满船清梦压星河
github
email
telegram

spring AI (五) 本地部署模型-llama3

在前面幾章,我們使用的都是 ChatGPT 這種線上的大模型,即使各類大模型的 API 價格是越來越低的,但是在大量調用時依然是筆不小的支出,更不要說還有各種限制。而本地部署一些小模型在很多情況下也能提供一些不錯的效果,不僅能夠降低成本,限制還更少,最重要的是還能夠對其進行一些自定義。這對公司及組織來說無疑是更有吸引力的。

本節將介紹使用 Ollama 來安裝部署最新的 Llama3 模型,並使用 spring AI 進行調用。

安裝 Ollama#

Ollama 的安裝很簡單,在官網 下載對應操作系統的安裝包安裝即可。安裝並啟動後,系統托盤會顯示圖標。

image

然後打開終端,輸入 ollama -v 驗證版本

image

此時 Ollama 已經安裝好了,但它僅支持命令行的方式對話。如果想要在界面中使用可以安裝一個 webUI(如果只是代碼調用,此步可以省略)

windows 系統,如果已經安裝了 WSLDocker 那可以使用以下命令運行

(1) 在 CPU 下運行:

docker run -d -p 3000:8080 --add-host=host.docker.internal:host-gateway -v open-webui:/app/backend/data --name open-webui --restart always ghcr.io/open-webui/open-webui:main

(2) 支持 GPU 運行:

docker run -d -p 3000:8080 --gpus=all -v ollama:/root/.ollama -v open-webui:/app/backend/data --name open-webui --restart always ghcr.io/open-webui/open-webui:ollama

安裝完成通過本地地址:http://127.0.0.1:3000 進行訪問

已知問題

在 WSL 中安裝此鏡像後,無法直接在線下載模型,只能讀取本地已安裝的模型,但在模型選擇完成對話依然失敗。不確定是否是 WSL 和 Windows 宿主機的端口映射的問題。
在 Windows 宿主機中安裝Docker Desktop再運行此鏡像就沒有問題,如果想使用 GPU 運行,也許要安裝 Conda 環境。

Llama3 模型#

要安裝模型,可以先在Ollama 官網進行搜索

image

原生的 llama3 主要是使用英文訓練的,雖然可以用 prompt 強制指定它使用中文回答,但也經常被忽略。因此我更推薦使用一些用中文微調的 llama3 model

你可以搜索 llama3-chinese 來找到中文微調的版本

image

wangshenzhi/llama3-8b-chinese-chat-ollama-q8 這個我使用的模型舉例

image

複製此處命令在終端運行即可

在大多數情況下僅推薦安裝 8B 的模型,更大的模型最好安裝在專用的計算卡上。

ollama run wangshenzhi/llama3-8b-chinese-chat-ollama-q8

ollama run 表示運行模型,如果模型未下載,則會先進行下載,如果模型已下載就會直接運行。

image

此時可以直接在終端進行對話

image

框架調用#

根據上一節的學習,我們可以直接復用代碼,只需要修改模型的配置即可

private static OllamaChatClient getClient(){
        var ollamaApi = new OllamaApi();

        return new OllamaChatClient(ollamaApi).withDefaultOptions(OllamaOptions.create()
                        .withModel("wangshenzhi/llama3-8b-chinese-chat-ollama-q8")
                        .withTemperature(0.4f));
    }

其中 OllamaApi 默認的 baseUrl 是 http://localhost:11434 如果你的模型不是部署在本地,需要修改這個地址
.withModel("wangshenzhi/llama3-8b-chinese-chat-ollama-q8") 表示指定模型,注意一定要是全名稱

其它的不需要更改,直接運行即可

image

llama3 的已知問題是:1. 模型太小,回復經常錯誤或產生幻覺、2. 其似乎並不支持 Function Calling

完整代碼如下

/**
 * @author lza
 * @date 2024/04/22-10:31
 **/

@RestController
@RequestMapping("ollama")
@RequiredArgsConstructor
@CrossOrigin
public class OllamaController {


    private static final Integer MAX_MESSAGE = 10;

    private static Map<String, List<Message>> chatMessage = new ConcurrentHashMap<>();


    /**
     * 創建OpenAiChatClient
     * @return OpenAiChatClient
     */
    private static OllamaChatClient getClient(){
        var ollamaApi = new OllamaApi();

        return new OllamaChatClient(ollamaApi).withDefaultOptions(OllamaOptions.create()
                        .withModel("wangshenzhi/llama3-8b-chinese-chat-ollama-q8")
                        .withTemperature(0.4f));
    }


    /**
     * 返回提示詞
     * @param message 用戶輸入的消息
     * @return Prompt
     */
    private List<Message> getMessages(String id, String message) {
        String systemPrompt = "{prompt}";
        SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(systemPrompt);

        Message userMessage = new UserMessage(message);

        Message systemMessage = systemPromptTemplate.createMessage(MapUtil.of("prompt", "you are a helpful AI assistant"));

        List<Message> messages = chatMessage.get(id);


        // 如果未獲取到消息,則創建新的消息並將系統提示和用戶輸入的消息添加到消息列表中
        if (messages == null){
            messages = new ArrayList<>();
            messages.add(systemMessage);
            messages.add(userMessage);
        } else {
            messages.add(userMessage);
        }

        return messages;
    }

    /**
     * 初始化函數調用
     * @return ChatOptions
     */
    private ChatOptions initFunc(){
        return OpenAiChatOptions.builder().withFunctionCallbacks(List.of(
                FunctionCallbackWrapper.builder(new MockWeatherService()).withName("weather").withDescription("Get the weather in location").build(),
                FunctionCallbackWrapper.builder(new WbHotService()).withName("wbHot").withDescription("Get the hot list of Weibo").build(),
                FunctionCallbackWrapper.builder(new TodayNews()).withName("todayNews").withDescription("60s watch world news").build(),
                FunctionCallbackWrapper.builder(new DailyEnglishFunc()).withName("dailyEnglish").withDescription("A daily inspirational sentence in English").build())).build();
    }

    /**
     * 創建連接
     */
    @SneakyThrows
    @GetMapping("/init/{message}")
    public String init() {
        return String.valueOf(UUID.randomUUID());
    }

    @GetMapping("chat/{id}/{message}")
    public SseEmitter chat(@PathVariable String id, @PathVariable String message, HttpServletResponse response) {

        response.setHeader("Content-type", "text/html;charset=UTF-8");
        response.setCharacterEncoding("UTF-8");

        OllamaChatClient client = getClient();
        SseEmitter emitter = SseEmitterUtils.connect(id);
        List<Message> messages = getMessages(id, message);
        System.err.println("chatMessage大小: " + messages.size());
        System.err.println("chatMessage: " + chatMessage);

        if (messages.size() > MAX_MESSAGE){
            SseEmitterUtils.sendMessage(id, "對話次數過多,請稍後重試🤔");
        }else {
            // 獲取模型的輸出流
            Flux<ChatResponse> stream = client.stream(new Prompt(messages));

            // 把流裡面的消息使用SSE發送
            Mono<String> result = stream
                    .flatMap(it -> {
                        StringBuilder sb = new StringBuilder();
                        String content = it.getResult().getOutput().getContent();
                        Optional.ofNullable(content).ifPresent(r -> {
                            SseEmitterUtils.sendMessage(id, content);
                            sb.append(content);
                        });
                        return Mono.just(sb.toString());
                    })
                    // 將消息拼接成字符串
                    .reduce((a, b) -> a + b)
                    .defaultIfEmpty("");

            // 將消息存儲到chatMessage中的AssistantMessage
            result.subscribe(finalContent -> messages.add(new AssistantMessage(finalContent)));

            // 將消息存儲到chatMessage中
            chatMessage.put(id, messages);

        }
        return emitter;

    }

白嫖#

以下是一些免費的 llama3 模型和 API

Groq 馬斯克家的,聽說用的不是英偉達的卡,而是專用的,速度非常快。
NVIDIA 老黃家的,裡面有很多免費的模型以供使用。
cloudflare 大善人 CF 家的,也有很多模型可供部署,有免費額度。

Ollama
本地部署 Llama3 – 8B/70B 大模型!最簡單的方法: 支持 CPU /GPU 運行 【3 種方案】
輕鬆搭建 llama3Web 交互界面 - Ollama + Open WebUI
使用 ollama + AnythingLLM 快速且簡單的在本地部署 llama3
Ollama:本地大模型運行指南

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。