Estrutura interna do Xinference#

Visão geral#

Xinference utiliza o framework de programação actor Xoscar, que projetamos, como seu componente principal para gerenciar máquinas, dispositivos e processos de inferência de modelos. Cada actor é uma unidade básica de inferência de modelos, e vários backends de inferência podem ser integrados nos actors, permitindo-nos suportar múltiplos mecanismos de inferência e hardwares. Esses actors são hospedados e escalonados em um pool de actors, que funciona como um pool de recursos. O design dos actors é assíncrono e não bloqueante.

actor

O supervisor e o worker são instâncias de atores. Primeiro, é necessário criar um pool de atores em cada servidor, que funciona como um pool de recursos; cada ator pode utilizar um núcleo de CPU ou um dispositivo GPU. Cada servidor possui seu próprio endereço (endereço IP ou nome de host), portanto, atores em diferentes nós de computação podem se comunicar entre si por meio desses endereços. Para mais informações, consulte Actor.

RESTful API#

A API RESTful é implementada utilizando FastAPI, e o código específico está em api/restful_api.py.

self._router.add_api_route("/status", self.get_status, methods=["GET"])

Este é um exemplo de API. A API /status corresponde à função get_status. Você pode adicionar a relação entre a API RESTful e a função de backend correspondente em api/restful_api.py.

Linha de comando#

A interface de linha de comando é implementada através do Click, com o código específico em deploy/cmdline.py. A linha de comando permite que os usuários interajam diretamente com o Xinference pelo terminal.

Entrada#

Com o exemplo da linha de comando que implementamos:

  • xinference: fornece comandos para gerenciamento de modelos, incluindo registrar/cancelar registro de modelos, listar todos os modelos registrados/em execução, além de iniciar ou encerrar modelos específicos. Também oferece comandos interativos, como geração de linguagem e bate-papo, para testar ou interagir com modelos implantados.

  • xinference-local: inicie um serviço local do Xinference.

  • xinference-supervisor: Inicia o processo supervisor, que gerencia e monitora os workers actors em um ambiente distribuído.

  • xinference-worker: inicia o processo worker, utilizando os recursos computacionais disponíveis para executar as tarefas atribuídas pelo supervisor.

Cada comando é acompanhado por option e flag, que permitem personalizar seu comportamento, como especificar o nível de log, endereço do host, número da porta e outras configurações relacionadas.

Os projetos Python definem pontos de entrada de console de linha de comando em setup.cfg ou setup.py.

console_scripts =
    xinference = xinference.deploy.cmdline:cli
    xinference-local = xinference.deploy.cmdline:local
    xinference-supervisor = xinference.deploy.cmdline:supervisor
    xinference-worker = xinference.deploy.cmdline:worker

A linha de comando xinference pode ser consultada no código em xinference.deploy.cmdline:cli.

Click#

Usamos o Click para implementar uma linha de comando específica:

@click.option(
      "--host",
      "-H",
      default=XINFERENCE_DEFAULT_DISTRIBUTED_HOST,
      type=str,
      help="Specify the host address for the supervisor.",
  )
  @click.option(
      "--port",
      "-p",
      default=XINFERENCE_DEFAULT_ENDPOINT_PORT,
      type=int,
      help="Specify the port number for the Xinference web ui and service.",
  )

Por exemplo, o comando xinference-local permite que você defina o endereço do host e a porta.

Actor#

Xinference é baseado no Xoscar, que é nosso framework de atores, capaz de gerenciar recursos computacionais e processos Python, suportando programação concorrente escalável. O pseudocódigo abaixo demonstra o princípio de funcionamento do Worker Actor, sendo que o Worker Actor real é muito mais complexo que isso.

import xoscar as xo

class WorkerActor(xo.Actor):
    def __init__(self, *args, **kwargs):
        ...
    async def launch_model(self, model_id, n_gpu, ...):
        # launch an inference engine, use specific model class to load model checkpoints
        ...
    async def list_models(self):
        # list models on this actor
        ...
    async def terminate_model(self, model_id):
        # terminate the model
        ...
    async def __post_create__(self):
        # called after the actor instance is created
        ...
    async def __pre_destroy__(self):
        # called before the actor instance is destroyed
        ...

Tomamos WorkerActor como exemplo para explicar como construir o Xinference. Cada classe de actor é uma classe Python padrão que herda de xoscar.Actor. A instância dessa classe é um actor específico dentro do pool de actors.

  • Definindo o comportamento do Actor: cada actor precisa definir determinadas ações ou comportamentos para concluir tarefas específicas. Por exemplo, o WorkerActor de inferência de modelo precisa iniciar o modelo (launch_model), listar os modelos neste actor (list_models) e encerrar o modelo (terminate_model). Dois métodos especiais merecem atenção. O __post_create__ é chamado antes da criação do actor para realizar a inicialização necessária. Já o __pre_destroy__ é chamado após a destruição do actor para executar tarefas de limpeza.

  • Referenciando um Actor e chamando métodos: ao criar um Actor, uma variável de referência é gerada para que outros Actors possam referenciá-lo. Actors também podem ser referenciados por endereço IP. Supondo que WorkerActor seja criado e a variável de referência seja worker_ref, é possível chamar o método launch_model dessa classe Actor através de worker_ref.launch_model(). Mesmo que o método dentro do actor fosse originalmente um método bloqueante tradicional, ao chamá-lo usando a variável de referência, ele se torna um método assíncrono.

  • Motor de Inferência: O Actor pode gerenciar processos, e o motor de inferência também é um tipo de processo. Na parte do modelo de inicialização do WorkerActor, podemos inicializar diferentes motores de inferência de acordo com as necessidades do usuário. Portanto, o Xinference pode suportar vários motores de inferência e se adaptar facilmente a novos motores de inferência no futuro.

Consulte a documentação do Xoscar para mais exemplos de uso de Actors.

Programação assíncrona#

Xinference e Xoscar dependem fortemente da biblioteca de programação assíncrona asyncio. A programação assíncrona é um paradigma de programação não bloqueante. Em comparação com chamadas de função bloqueantes tradicionais, as requisições ou chamadas de função na programação assíncrona são executadas em segundo plano, com os resultados retornados em algum momento futuro. A vantagem da programação assíncrona é permitir que muitas atividades ou tarefas diferentes sejam realizadas simultaneamente de forma concorrente.

Se você não está familiarizado com o asyncio do Python, pode consultar mais tutoriais para obter ajuda:

model#

Xinference suporta diferentes tipos de modelos, incluindo modelos de linguagem de grande escala (LLM), modelos de imagem, modelos de áudio, modelos de incorporação, entre outros. Todos os modelos são implementados na pasta model/.

LLM#

Usando model/llm/ como exemplo, ele gerencia e inicia principalmente o LLM, incluindo carregamento, configuração e execução de modelos de linguagem de grande porte.

Damos suporte a diferentes backends de inferência, como GGML, PyTorch e vLLM. O conteúdo gerado é compatível com o formato da OpenAI, como suporte a saída em streaming (stream) e modelos de diálogo retornados no formato chat completion. Portanto, após a saída do modelo, é necessário realizar muitas adaptações. Essas tarefas não são difíceis, mas exigem algum tempo. Ao escrever essa parte do código, consulte a documentação da API da OpenAI e a documentação de cada backend de inferência para fazer as adaptações necessárias.

JSON#

Em model/llm/llm_family.json, utilizamos um arquivo JSON para gerenciar os metadados de novos modelos de código aberto que surgem. Adicionar um novo modelo não requer escrever novo código, basta adicionar os novos metadados ao arquivo JSON existente.

{
    "model_name": "llama-2-chat",
    "model_ability": ["chat"],
    "model_specs": [
        {
            "model_format": "ggmlv3",
            "model_size_in_billions": 70,
            "quantization": ["q8_0", ...],
            "model_id": "TheBloke/Llama-2-70B-Chat-GGML",
        },
        ...
    ],
    "prompt_style": {
        "style_name": "LLAMA2",
        "system_prompt": "<s>[INST] <<SYS>>\nYou are a helpful AI assistant.\n<</SYS>>\n\n",
        "roles": ["[INST]", "[/INST]"],
        "stop_token_ids": [2],
        "stop": ["</s>"]
    }
}

Aqui está um exemplo de como definir um modelo Llama-2 para chat. O model_specs define as informações do modelo, pois uma família de modelos geralmente possui diferentes tamanhos, métodos de quantização e formatos de arquivo. Por exemplo, model_format pode ser pytorch (usando Hugging Face Transformers ou vLLM como backend), ggmlv3 (biblioteca de tensores relacionada ao llama.cpp) ou gptq (framework de quantização pós-treinamento). O model_id define o repositório do hub de modelos, onde o Xinference baixa os arquivos de checkpoint. Além disso, devido a diferentes processos de ajuste de instruções, diferentes famílias de modelos têm estilos de prompt distintos. O prompt_style no arquivo JSON especifica o formato de prompt para aquele modelo específico. Por exemplo, system_prompt e roles são usados para especificar as instruções e a personalidade do modelo.

Guia de Código#

O código principal está localizado em xinference/:

  • api/restful_api.py é a parte principal para configurar e executar a API RESTful. Ela integra um serviço de autenticação (com o código específico localizado em oauth2/), pois algumas ou todas as portas exigem autenticação do usuário.

  • client/:Este é o cliente do Xinference.

    • oscar/ define o cliente Actor, que é uma interface cliente para interagir com modelos no Xinference.

    • restful/ implementa o cliente RESTful para interagir com o serviço Xinference.

  • core/: Esta é a parte central do Xinference.

    • metrics.py e resource.py definem um conjunto de ferramentas para coletar e relatar métricas e o estado dos recursos do nó, incluindo vazão do modelo, latência, uso de CPU e GPU, uso de memória, entre outros.

    • image_interface.py e chat_interface.py implementam a interface Gradio para modelos de imagem e chat, respectivamente. Essas interfaces permitem que os usuários interajam com os modelos por meio de uma interface web, como gerar imagens ou realizar conversas. O código utiliza o pacote Gradio para construir a interface do usuário e se comunica com os modelos de backend por meio de nossa API RESTful.

    • worker.py e supervisor.py definem, respectivamente, a lógica do worker actor e do supervisor actor. O worker actor é responsável por executar tarefas específicas de computação de modelos, enquanto o supervisor actor gerencia o ciclo de vida dos nós Worker e a programação de tarefas, além de monitorar o estado do sistema.

    • status_guard.py implementa um monitor de status para rastrear o estado do modelo (como criação, atualização, término, etc.). Ele permite consultar as informações de status de uma instância de modelo com base no UID do modelo.

    • cache_tracker.py define um rastreador de cache, usado para registrar e gerenciar o status do cache e as informações de versão do modelo. Ele suporta o registro do local do cache e do status da versão do modelo, além de consultar informações de versão do modelo com base no nome do modelo.

    • event.py define um coletor de eventos, usado para coletar e relatar vários eventos de modelos em execução, como informações, avisos e erros. model.py define um ator de modelo, que é o componente central para interagir diretamente com o modelo. O ator de modelo é responsável por executar requisições de inferência do modelo, processar fluxos de dados de entrada e saída, e oferecer suporte a várias operações do modelo. Ambas as partes utilizam Xoscar para execução concorrente e distribuída.

  • deploy/: fornece uma interface de linha de comando (CLI) para interagir com o framework Xinference, permitindo que os usuários executem operações via linha de comando. Para mais informações, consulte Command Line.

  • locale/: suporta localização multilíngue. Basta adicionar e atualizar os arquivos de tradução JSON para oferecer suporte a mais idiomas, melhorando a experiência do usuário.

  • model/: Ele fornece uma estrutura para descrição, criação e cache de modelos. Consulte Model para obter mais informações.

  • web/ui/:código JS do frontend (interface do usuário).