Hướng dẫn triển khai cục bộ mô hình GLM-4-9B-Chat

Trong bối cảnh trí tuệ nhân tạo đang phát triển mạnh mẽ, việc triển khai các mô hình học sâu đã trở thành chủ đề nghiên cứu và ứng dụng thực tiễn được quan tâm hàng đầu. Lĩnh vực xử lý ngôn ngữ tự nhiên (NLP), đặc biệt là các hệ thống hội thoại, đang nhanh chóng trở thành cốt lõi của các ứng dụng thông minh. Mô hình GLM-4-9B-Chat với khả năng hiểu và sinh ngôn ngữ vượt trội, tạo nền tảng vững chắc cho việc xây dựng hệ thống hội thoại thông minh. Tuy nhiên, việc triển khai mô hình đòi hỏi nhiều bước từ cấu hình môi trường, quản lý phụ thuộc đến viết mã. Bài viết này hướng dẫn chi tiết quá trình triển khai GLM-4-9B-Chat thông qua suy luận cục bộ và xuất bản dịch vụ API theo chuẩn OpenAI.

Giới thiệu về GLM-4-9B-Chat

GLM-4-9B là phiên bản mới nhất trong dòng GLM-4 do Zhipu AI phát hành dưới dạng mã nguồn mở, dẫn đầu xu hướng mô hình tiền huấn luyện. Trong các đánh giá trên nhiều tập dữ liệu bao gồm hiểu ngữ nghĩa, tính toán toán học, suy luận logic, lập trình và kiến thức tổng quát, GLM-4-9B cùng phiên bản tối ưu theo sở thích người dùng GLM-4-9B-Chat đều thể hiện hiệu suất xuất sắc.

GLM-4-9B-Chat không chỉ thành thạo đa hội thoại mà còn sở hữu các tính năng nâng cao như duyệt web, thực thi mã, gọi công cụ tùy chỉnh (Function Call) và suy luận văn bản dài (hỗ trợ ngữ cảnh lên đến 128K token). Đặc biệt, thế hệ mô hình này đạt đột phá trong xử lý đa ngôn ngữ, hỗ trợ 26 ngôn ngữ bao gồm tiếng Nhật, tiếng Hàn, tiếng Đức, tạo điều kiện giao tiếp xuyên văn hóa.

Để đáp ứng nhu cầu ứng dụng đa dạng, phiên bản nâng cấp hỗ trợ ngữ cảnh 1M token (khoảng 2 triệu ký tự tiếng Trung) cũng được phát hành, mở rộng giới hạn xử lý thông minh cho các kịch bản phức tạp.

Đánh giá hiệu suất

Trong các bài kiểm tra nghiêm ngặt trên nhiều tác vụ kinh điển, GLM-4-9B-Chat vượt trội hơn hẳn Llama-3-8B-Instruct và ChatGLM3-6B, khẳng định vị thế dẫn đầu trong lĩnh vực trí tuệ nhân tạo.

Khả năng đa ngôn ngữ

Trên các tập dữ liệu đa ngôn ngữ gồm 6 ngôn ngữ khác nhau, GLM-4-9B-Chat thể hiện hiệu suảt ấn tượng khi so sánh với Llama-3-8B-Instruct.

Khả năng gọi công cụ

Tại bảng xếp hạng Berkeley Function Calling Leaderboard, GLM-4-9B-Chat cạnh tranh sát sao với gpt-4-turbo, thể hiện sức mạnh và độ chính xác vượt trội trong tác vụ gọi hàm.

Chuẩn bị môi trường

Có thể thuê thiết bị tính toán hiệu năng cao qua nền tảng AutoDL, trang bị card đồ họa 24GB VRAM như NVIDIA RTX 4090. Khuyến nghị chọn image "PyTorch-2.1.0-3.10(ubuntu22.04)-12.1" đã tối ưu cho Python 3.10 với PyTorch 2.1.0 cài sẵn, đảm bảo tính ổn định và tương thích.

Tải mã nguồn từ GitHub

git clone https://github.com/THUDM/GLM-4.git

Cài đặt các phụ thuộc

1. Nâng cấp pip

python -m pip install --upgrade pip

2. Thay đổi nguồn PyPI

Để tăng tốc độ cài đặt, sử dụng mirror TUNA của Đại học Thanh Hoa:

pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple

3. Cài đặt các gói thư viện

cd GLM-4/basic_demo/
pip install -r requirements.txt

Nội dung file requirements.txt:

torch>=2.3.0
torchvision>=0.18.0
transformers>=4.42.4
huggingface-hub>=0.24.0
sentencepiece>=0.2.0
jinja2>=3.1.4
pydantic>=2.8.2
timm>=1.0.7
tiktoken>=0.7.0
accelerate>=0.32.1
sentence_transformers>=3.0.1
gradio>=4.38.1
openai>=1.35.0
einops>=0.8.0
pillow>=10.4.0
sse-starlette>=2.1.2
bitsandbytes>=0.43.1

Tải file mô hình

Sử dụng hàm snapshot_download từ ModelScope để tải mô hình. Tham số cache_dir chỉ định đường dẫn lưu trữ.

Tạo file /root/autodl-tmp/fetch-model.py với nội dung:

import torch
from modelscope import snapshot_download, AutoModel, AutoTokenizer
import os

model_path = snapshot_download(
    'ZhipuAI/glm-4-9b-chat', 
    cache_dir='/root/autodl-tmp', 
    revision='master'
)

Chạy python /root/autodl-tmp/fetch-model.py để tải. Dung lượng mô hình khoảng 18GB, thời gian tải từ 10-20 phút.

Kiểm thử suy luận cục bộ

1. Nạp mô hình

import torch
from transformers import AutoModelForCausalLM, AutoTokenizer

compute_device = "cuda"
model_location = '/root/autodl-tmp/ZhipuAI/glm-4-9b-chat'

token_processor = AutoTokenizer.from_pretrained(
    model_location, 
    trust_remote_code=True
)

language_model = AutoModelForCausalLM.from_pretrained(
    model_location,
    torch_dtype=torch.bfloat16,
    low_cpu_mem_usage=True,
    trust_remote_code=True
).to(compute_device).eval()

2. Chuẩn bị đầu vào

user_prompt = "Hãy giới thiệu về mô hình AI lớn"

model_inputs = token_processor.apply_chat_template(
    [{"role": "user", "content": user_prompt}],
    add_generation_prompt=True,
    tokenize=True,
    return_tensors="pt",
    return_dict=True
)

model_inputs = model_inputs.to(compute_device)

3. Sinh văn bản

generation_config = {
    "max_length": 2500,
    "do_sample": True,
    "top_k": 1
}

with torch.no_grad():
    generated_ids = language_model.generate(**model_inputs, **generation_config)
    generated_ids = generated_ids[:, model_inputs['input_ids'].shape[1]:]
    
    result_text = token_processor.decode(
        generated_ids[0], 
        skip_special_tokens=True
    )
    print(result_text)

Kiểm thử dịch vụ API theo chuẩn OpenAI

1. Điều chỉnh đường dẫn mô hình

Chỉnh sửa file openai_api_server.py trong thư mục GLM-4/basic_demo/, cập nhật biến môi trường:

MODEL_PATH = os.environ.get('MODEL_PATH', '/root/autodl-tmp/ZhipuAI/glm-4-9b-chat')

Đoạn mã máy chủ API hoàn chỉnh:

import time
import re
import uvicorn
import gc
import json
import torch
import random
import string
import os

from vllm import SamplingParams, AsyncEngineArgs, AsyncLLMEngine
from fastapi import FastAPI, HTTPException, Response
from fastapi.middleware.cors import CORSMiddleware
from contextlib import asynccontextmanager
from typing import List, Literal, Optional, Union
from pydantic import BaseModel, Field
from transformers import AutoTokenizer, LogitsProcessor
from sse_starlette.sse import EventSourceResponse
from asyncio.log import logger

EventSourceResponse.DEFAULT_PING_INTERVAL = 1000

MODEL_PATH = os.environ.get('MODEL_PATH', '/root/autodl-tmp/ZhipuAI/glm-4-9b-chat')
MAX_MODEL_LENGTH = 8192


@asynccontextmanager
async def lifespan(app: FastAPI):
    yield
    if torch.cuda.is_available():
        torch.cuda.empty_cache()
        torch.cuda.ipc_collect()


web_app = FastAPI(lifespan=lifespan)

web_app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)


def create_identifier(prefix: str, length=29) -> str:
    random_suffix = ''.join(random.choices(string.ascii_letters + string.digits, k=length))
    return f"{prefix}{random_suffix}"


class ModelInfo(BaseModel):
    id: str = ""
    object: str = "model"
    created: int = Field(default_factory=lambda: int(time.time()))
    owned_by: str = "owner"
    root: Optional[str] = None
    parent: Optional[str] = None
    permission: Optional[list] = None


class ModelCollection(BaseModel):
    object: str = "list"
    data: List[ModelInfo] = ["glm-4"]


class ToolInvocation(BaseModel):
    name: Optional[str] = None
    arguments: Optional[str] = None


class ToolFunctionDetails(BaseModel):
    name: Optional[str] = None
    arguments: Optional[str] = None


class TokenUsage(BaseModel):
    prompt_tokens: int = 0
    total_tokens: int = 0
    completion_tokens: Optional[int] = 0


class ToolCallDetails(BaseModel):
    index: Optional[int] = 0
    id: Optional[str] = None
    function: ToolInvocation
    type: Optional[Literal["function"]] = 'function'


class ConversationMessage(BaseModel):
    role: Literal["user", "assistant", "system", "tool"]
    content: Optional[str] = None
    function_call: Optional[ToolFunctionDetails] = None
    tool_calls: Optional[List[ToolCallDetails]] = None


class MessageDelta(BaseModel):
    role: Optional[Literal["user", "assistant", "system"]] = None
    content: Optional[str] = None
    function_call: Optional[ToolFunctionDetails] = None
    tool_calls: Optional[List[ToolCallDetails]] = None


class CompletionChoice(BaseModel):
    index: int
    message: ConversationMessage
    finish_reason: Literal["stop", "length", "tool_calls"]


class StreamChoice(BaseModel):
    delta: MessageDelta
    finish_reason: Optional[Literal["stop", "length", "tool_calls"]]
    index: int


class ChatResponse(BaseModel):
    model: str
    id: Optional[str] = Field(default_factory=lambda: create_identifier('chatcmpl-', 29))
    object: Literal["chat.completion", "chat.completion.chunk"]
    choices: List[Union[CompletionChoice, StreamChoice]]
    created: Optional[int] = Field(default_factory=lambda: int(time.time()))
    system_fingerprint: Optional[str] = Field(default_factory=lambda: create_identifier('fp_', 9))
    usage: Optional[TokenUsage] = None


class ChatRequest(BaseModel):
    model: str
    messages: List[ConversationMessage]
    temperature: Optional[float] = 0.8
    top_p: Optional[float] = 0.8
    max_tokens: Optional[int] = None
    stream: Optional[bool] = False
    tools: Optional[Union[dict, List[dict]]] = None
    tool_choice: Optional[Union[str, dict]] = None
    repetition_penalty: Optional[float] = 1.1


class ScoreValidator(LogitsProcessor):
    def __call__(
            self, input_ids: torch.LongTensor, scores: torch.FloatTensor
    ) -> torch.FloatTensor:
        if torch.isnan(scores).any() or torch.isinf(scores).any():
            scores.zero_()
            scores[..., 5] = 5e4
        return scores


def parse_model_output(output: str, tools: dict | List[dict] = None, use_tool: bool = False) -> Union[str, dict]:
    lines = output.strip().split("\n")
    args_json = None
    builtin_tools = ["cogview", "simple_browser"]
    available_tools = {tool['function']['name'] for tool in tools} if tools else {}

    if len(lines) >= 2 and lines[1].startswith("{"):
        func_name = lines[0].strip()
        args_str = "\n".join(lines[1:]).strip()
        if func_name in available_tools or func_name in builtin_tools:
            try:
                args_json = json.loads(args_str)
                is_tool = True
            except json.JSONDecodeError:
                is_tool = func_name in builtin_tools

            if is_tool and use_tool:
                result = {
                    "name": func_name,
                    "arguments": json.dumps(args_json if isinstance(args_json, dict) else args_str, ensure_ascii=False)
                }
                if func_name == "simple_browser":
                    search_regex = re.compile(r'search\("(.+?)"\s*,\s*recency_days\s*=\s*(\d+)\)')
                    match = search_regex.match(args_str)
                    if match:
                        result["arguments"] = json.dumps({
                            "query": match.group(1),
                            "recency_days": int(match.group(2))
                        }, ensure_ascii=False)
                elif func_name == "cogview":
                    result["arguments"] = json.dumps({"prompt": args_str}, ensure_ascii=False)

                return result
    return output.strip()


@torch.inference_mode()
async def stream_generate(params):
    messages = params["messages"]
    tools = params["tools"]
    tool_choice = params["tool_choice"]
    temp = float(params.get("temperature", 1.0))
    repeat_penalty = float(params.get("repetition_penalty", 1.0))
    nucleus_p = float(params.get("top_p", 1.0))
    max_new = int(params.get("max_tokens", 8192))

    processed_msgs = transform_messages(messages, tools=tools, tool_choice=tool_choice)
    prompt_text = token_processor.apply_chat_template(processed_msgs, add_generation_prompt=True, tokenize=False)
    
    sampling_cfg = {
        "n": 1,
        "best_of": 1,
        "presence_penalty": 1.0,
        "frequency_penalty": 0.0,
        "temperature": temp,
        "top_p": nucleus_p,
        "top_k": -1,
        "repetition_penalty": repeat_penalty,
        "use_beam_search": False,
        "length_penalty": 1,
        "early_stopping": False,
        "stop_token_ids": [151329, 151336, 151338],
        "ignore_eos": False,
        "max_tokens": max_new,
        "logprobs": None,
        "prompt_logprobs": None,
        "skip_special_tokens": True,
    }
    sampling_params = SamplingParams(**sampling_cfg)
    
    async for output in inference_engine.generate(
        inputs=prompt_text, 
        sampling_params=sampling_params, 
        request_id=f"{time.time()}"
    ):
        out_len = len(output.outputs[0].token_ids)
        in_len = len(output.prompt_token_ids)
        yield {
            "text": output.outputs[0].text,
            "usage": {
                "prompt_tokens": in_len,
                "completion_tokens": out_len,
                "total_tokens": out_len + in_len
            },
            "finish_reason": output.outputs[0].finish_reason,
        }
    
    gc.collect()
    torch.cuda.empty_cache()


def transform_messages(messages, tools=None, tool_choice="none"):
    original_msgs = messages
    transformed = []
    has_system = False

    def filter_by_choice(tool_choice, tools):
        target_name = tool_choice.get('function', {}).get('name', None)
        if not target_name:
            return []
        return [t for t in tools if t.get('function', {}).get('name') == target_name]

    if tool_choice != "none":
        if isinstance(tool_choice, dict):
            tools = filter_by_choice(tool_choice, tools)
        if tools:
            transformed.append({"role": "system", "content": None, "tools": tools})
            has_system = True

    if isinstance(tool_choice, dict) and tools:
        transformed.append({
            "role": "assistant",
            "metadata": tool_choice["function"]["name"],
            "content": ""
        })

    for msg in original_msgs:
        role, content, func_call = msg.role, msg.content, msg.function_call
        tool_calls = getattr(msg, 'tool_calls', None)

        if role == "function":
            transformed.append({"role": "observation", "content": content})
        elif role == "tool":
            transformed.append({"role": "observation", "content": content, "function_call": True})
        elif role == "assistant":
            if tool_calls:
                for tc in tool_calls:
                    transformed.append({
                        "role": "assistant",
                        "metadata": tc.function.name,
                        "content": tc.function.arguments
                    })
            else:
                for resp in content.split("\n"):
                    if "\n" in resp:
                        meta, sub = resp.split("\n", maxsplit=1)
                    else:
                        meta, sub = "", resp
                    transformed.append({
                        "role": role,
                        "metadata": meta,
                        "content": sub.strip()
                    })
        else:
            if role == "system" and has_system:
                has_system = False
                continue
            transformed.append({"role": role, "content": content})

    if not tools or tool_choice == "none":
        for m in original_msgs:
            if m.role == 'system':
                transformed.insert(0, {"role": m.role, "content": m.content})
                break
    return transformed


@web_app.get("/health")
async def health_check() -> Response:
    return Response(status_code=200)


@web_app.get("/v1/models", response_model=ModelCollection)
async def list_available_models():
    model_card = ModelInfo(id="glm-4")
    return ModelCollection(data=[model_card])


@web_app.post("/v1/chat/completions", response_model=ChatResponse)
async def handle_completion(request: ChatRequest):
    if len(request.messages) < 1 or request.messages[-1].role == "assistant":
        raise HTTPException(status_code=400, detail="Yêu cầu không hợp lệ")

    gen_cfg = dict(
        messages=request.messages,
        temperature=request.temperature,
        top_p=request.top_p,
        max_tokens=request.max_tokens or 1024,
        echo=False,
        stream=request.stream,
        repetition_penalty=request.repetition_penalty,
        tools=request.tools,
        tool_choice=request.tool_choice,
    )
    logger.debug(f"==== request ====\n{gen_cfg}")

    if request.stream:
        stream_gen = create_stream(request.model, gen_cfg)
        first_chunk = await anext(stream_gen)
        if first_chunk:
            return EventSourceResponse(stream_gen, media_type="text/event-stream")
        
        func_call = None
        if first_chunk and request.tools:
            try:
                func_call = parse_model_output(first_chunk, request.tools, use_tool=True)
            except:
                logger.warning("Phân tích tool call thất bại")

        if isinstance(func_call, dict):
            func_call_obj = ToolFunctionDetails(**func_call)
            response_stream = format_tool_response(request.model, first_chunk, function_call=func_call_obj)
            return EventSourceResponse(response_stream, media_type="text/event-stream")
        else:
            return EventSourceResponse(stream_gen, media_type="text/event-stream")
    
    final_response = ""
    async for resp in stream_generate(gen_cfg):
        final_response = resp

    if final_response["text"].startswith("\n"):
        final_response["text"] = final_response["text"][1:]
    final_response["text"] = final_response["text"].strip()

    usage_stats = TokenUsage()
    func_call, finish_reason, tool_calls_list = None, "stop", None
    
    if request.tools:
        try:
            func_call = parse_model_output(final_response["text"], request.tools, use_tool=True)
        except Exception as e:
            logger.warning(f"Lỗi phân tích tool call: {e}")
    
    if isinstance(func_call, dict):
        finish_reason = "tool_calls"
        func_detail = ToolFunctionDetails(**func_call)
        func_invocation = ToolInvocation(
            name=func_detail.name,
            arguments=func_detail.arguments
        )
        tool_calls_list = [ToolCallDetails(
            id=create_identifier('call_', 24),
            function=func_invocation,
            type="function"
        )]

    response_msg = ConversationMessage(
        role="assistant",
        content=None if tool_calls_list else final_response["text"],
        function_call=None,
        tool_calls=tool_calls_list,
    )

    choice_data = CompletionChoice(
        index=0,
        message=response_msg,
        finish_reason=finish_reason,
    )
    
    task_usage = TokenUsage.model_validate(final_response["usage"])
    for key, val in task_usage.model_dump().items():
        setattr(usage_stats, key, getattr(usage_stats, key) + val)

    return ChatResponse(
        model=request.model,
        choices=[choice_data],
        object="chat.completion",
        usage=usage_stats
    )


async def create_stream(model_id, gen_cfg):
    generated = ""
    is_tool_invocation = False
    sent_first = False
    created_ts = int(time.time())
    invoked_func_name = None
    resp_id = create_identifier('chatcmpl-', 29)
    sys_fp = create_identifier('fp_', 9)
    available_tools = {t['function']['name'] for t in gen_cfg['tools']} if gen_cfg['tools'] else {}
    delta_buffer = ""
    
    async for chunk in stream_generate(gen_cfg):
        decoded = chunk["text"]
        delta_buffer += decoded[len(generated):]
        generated = decoded
        lines = generated.strip().split("\n")

        if not is_tool_invocation and len(lines) >= 2:
            first_line = lines[0].strip()
            if first_line in available_tools:
                is_tool_invocation = True
                invoked_func_name = first_line
                delta_buffer = lines[1]

        if is_tool_invocation:
            if not sent_first:
                func_data = {"name": invoked_func_name, "arguments": ""}
                tool_call = ToolCallDetails(
                    index=0,
                    id=create_identifier('call_', 24),
                    function=ToolInvocation(**func_data),
                    type="function"
                )
                msg_delta = MessageDelta(
                    content=None,
                    role="assistant",
                    function_call=None,
                    tool_calls=[tool_call]
                )
                choice = StreamChoice(index=0, delta=msg_delta, finish_reason=None)
                chunk_resp = ChatResponse(
                    model=model_id,
                    id=resp_id,
                    choices=[choice],
                    created=created_ts,
                    system_fingerprint=sys_fp,
                    object="chat.completion.chunk"
                )
                yield ""
                yield chunk_resp.model_dump_json(exclude_unset=True)
                sent_first = True

            func_data = {"name": None, "arguments": delta_buffer}
            delta_buffer = ""
            tool_call = ToolCallDetails(
                index=0,
                id=None,
                function=ToolInvocation(**func_data),
                type="function"
            )
            msg_delta = MessageDelta(content=None, role=None, function_call=None, tool_calls=[tool_call])
            choice = StreamChoice(index=0, delta=msg_delta, finish_reason=None)
            chunk_resp = ChatResponse(
                model=model_id,
                id=resp_id,
                choices=[choice],
                created=created_ts,
                system_fingerprint=sys_fp,
                object="chat.completion.chunk"
            )
            yield chunk_resp.model_dump_json(exclude_unset=True)

        elif (gen_cfg["tools"] and gen_cfg["tool_choice"] != "none") or is_tool_invocation:
            continue

        else:
            finish = chunk.get("finish_reason", None)
            if not sent_first:
                msg_delta = MessageDelta(content="", role="assistant", function_call=None)
                choice = StreamChoice(index=0, delta=msg_delta, finish_reason=finish)
                chunk_resp = ChatResponse(
                    model=model_id,
                    id=resp_id,
                    choices=[choice],
                    created=created_ts,
                    system_fingerprint=sys_fp,
                    object="chat.completion.chunk"
                )
                yield chunk_resp.model_dump_json(exclude_unset=True)
                sent_first = True

            msg_delta = MessageDelta(content=delta_buffer, role="assistant", function_call=None)
            delta_buffer = ""
            choice = StreamChoice(index=0, delta=msg_delta, finish_reason=finish)
            chunk_resp = ChatResponse(
                model=model_id,
                id=resp_id,
                choices=[choice],
                created=created_ts,
                system_fingerprint=sys_fp,
                object="chat.completion.chunk"
            )
            yield chunk_resp.model_dump_json(exclude_unset=True)

    if is_tool_invocation:
        final_chunk = ChatResponse(
            model=model_id,
            id=resp_id,
            system_fingerprint=sys_fp,
            choices=[StreamChoice(
                index=0,
                delta=MessageDelta(content=None, role=None, function_call=None),
                finish_reason="tool_calls"
            )],
            created=created_ts,
            object="chat.completion.chunk",
            usage=None
        )
        yield final_chunk.model_dump_json(exclude_unset=True)
    elif delta_buffer != "":
        msg_delta = MessageDelta(content="", role="assistant", function_call=None)
        choice = StreamChoice(index=0, delta=msg_delta, finish_reason=None)
        chunk_resp = ChatResponse(
            model=model_id,
            id=resp_id,
            choices=[choice],
            created=created_ts,
            system_fingerprint=sys_fp,
            object="chat.completion.chunk"
        )
        yield chunk_resp.model_dump_json(exclude_unset=True)
        
        msg_delta = MessageDelta(content=delta_buffer, role="assistant", function_call=None)
        delta_buffer = ""
        choice = StreamChoice(index=0, delta=msg_delta, finish_reason='stop')
        chunk_resp = ChatResponse(
            model=model_id,
            id=resp_id,
            choices=[choice],
            created=created_ts,
            system_fingerprint=sys_fp,
            object="chat.completion.chunk"
        )
        yield chunk_resp.model_dump_json(exclude_unset=True)
        yield '[DONE]'
    else:
        yield '[DONE]'


async def format_tool_response(model_id: str, value: str, function_call: ToolFunctionDetails = None):
    delta = MessageDelta(role="assistant", content=value)
    if function_call is not None:
        delta.function_call = function_call

    choice = StreamChoice(index=0, delta=delta, finish_reason=None)
    response = ChatResponse(
        model=model_id,
        choices=[choice],
        object="chat.completion.chunk"
    )
    yield "{}".format(response.model_dump_json(exclude_unset=True))
    yield '[DONE]'


if __name__ == "__main__":
    token_processor = AutoTokenizer.from_pretrained(MODEL_PATH, trust_remote_code=True)
    engine_cfg = AsyncEngineArgs(
        model=MODEL_PATH,
        tokenizer=MODEL_PATH,
        tensor_parallel_size=1,
        dtype="bfloat16",
        trust_remote_code=True,
        gpu_memory_utilization=0.9,
        enforce_eager=True,
        worker_use_ray=False,
        engine_use_ray=False,
        disable_log_requests=True,
        max_model_len=MAX_MODEL_LENGTH,
    )
    inference_engine = AsyncLLMEngine.from_engine_args(engine_cfg)
    uvicorn.run(web_app, host='0.0.0.0', port=8000, workers=1)

2. Khởi động dịch vụ API

python openai_api_server.py

Dịch vụ khởi động thành công tại cổng 8000.

3. Gọi dịch vụ API

Sử dụng thư viện OpenAI trong Python:

from openai import OpenAI

api_endpoint = "http://127.0.0.1:8000/v1/"
api_client = OpenAI(api_key="EMPTY", base_url=api_endpoint)

conversation = [
    {"role": "system", "content": "Bạn là một trợ lý AI thông minh!"},
    {"role": "user", "content": "Bạn là ai?"}
]

api_response = api_client.chat.completions.create(
    model="glm-4",
    messages=conversation,
    stream=False,
    max_tokens=256,
    temperature=0.4,
    presence_penalty=1.2,
    top_p=0.8,
)
print(api_response)

Kết quả trả về:

ChatCompletion(
    id='chatcmpl-AJ56zgVTbBQb47Seewbva8Ye3mmP6',
    choices=[Choice(
        finish_reason='stop',
        index=0,
        logprobs=None,
        message=ChatCompletionMessage(
            content='Tôi là ChatGLM, một trợ lý AI được phát triển dựa trên mô hình ngôn ngữ do Phòng thí nghiệm KEG của Đại học Thanh Hoa và công ty Zhipu AI cùng huấn luyện năm 2024. Nhiệm vụ của tôi là cung cấp câu trả lời và hỗ trợ phù hợp cho các câu hỏi và yêu cầu của người dùng.',
            role='assistant',
            function_call=None,
            tool_calls=None
        )
    )],
    created=1722405795,
    model='glm-4',
    object='chat.completion',
    service_tier=None,
    system_fingerprint='fp_hawbL1lgv',
    usage=CompletionUsage(completion_tokens=45, prompt_tokens=25, total_tokens=70)
)

Qua quá trình thực hành, chúng ta đã triển khai thành công mô hình GLM-4-9B-Chat tại môi trường cục bộ và cung cấp dịch vụ API hiệu quả theo chuẩn OpenAI. Quy trình này trình bày cách xây dựng môi trường triển khai từ đầu, bao gồm viết mã, khởi động dịch vụ và gọi API.

Thẻ: GLM-4 Zhipu AI Transformers FastAPI vLLM

Đăng vào ngày 12 tháng 9 lúc 22:13