Ajuste fino supervisionado (Completo) e atendimento de Qwen3.5-0.8B
Faça o ajuste fino do Qwen3.5-0.8B-Base compacto de pesos abertos modelo de linguagem de grande escala no AI Runtime (GPU serverless) e, em seguida, implante-o por trás de um endpoint de Model Serving. Este exemplo é executado de ponta a ponta em uma única GPU H100 e mostra como:
- Execute o ajuste fino supervisionado (SFT) com o
SFTTrainerda TRL em um dataset de acompanhamento de instruções - Compare as respostas do modelo antes e depois do ajuste fino para ver o efeito do SFT
- Registre o modelo fine-tuned no Unity Catalog para governança e implantação
- Serve the model behind a Custom Foundation Model endpoint running a vLLM OpenAI-compatible server
Conceitos-chave:
- Supervised fine-tuning (SFT): Continues treinamento a base model on curated instruction/response pairs so it follows instructions in the target style
- TRL: A biblioteca para ajuste fino supervisionado e aprendizado por reforço de modelos de linguagem
- Custom Foundation Model serving: serve pesos de LLM próprios e ajustados em servindo modelo com suporte a GPU e API compatível com OpenAI
Este exemplo requer o ambiente AI Runtime versão 6 ou acima (a etapa de serving usa vLLM e flashinfer, que vêm incluídos na v6).
Connect to serverless GPU compute
Este notebook requer compute de GPU serverless. Para conectar:
- Clique no seletor de compute do notebook no canto superior direito e selecione Serverless GPU .
- No lado direito, clique no botão de ambiente.
- Select H100 as the Accelerator .
- Escolha AI v6 no ambiente base.
- Clique em Aplicar .
Configuração
A próxima célula define os widgets para o local do Unity Catalog onde o modelo ajustado (fine-tuned) é registrado. O modelo é registrado como {uc_catalog}.{uc_schema}.{uc_model_name} e o endpoint de serviço é nomeado {uc_model_name}-endpoint.
Defina deploy_endpoint como false para parar após o registro de modelo e o teste vLLM local, sem implantar um endpoint de serviço gerenciado.
dbutils.widgets.text("uc_catalog", "main")
dbutils.widgets.text("uc_schema", "default")
dbutils.widgets.text("uc_model_name", "qwen3_5_0_8b_sft")
dbutils.widgets.dropdown("deploy_endpoint", "true", ["true", "false"])
UC_CATALOG = dbutils.widgets.get("uc_catalog")
UC_SCHEMA = dbutils.widgets.get("uc_schema")
UC_MODEL_NAME_BASE = dbutils.widgets.get("uc_model_name")
# Whether to deploy the managed serving endpoint (Steps 8-9). Set to "false" to stop after
# registration and the local vLLM test (for example, on workspaces where entrypoint-based
# Custom Foundation Model serving is not enabled).
DEPLOY_ENDPOINT = dbutils.widgets.get("deploy_endpoint").lower() == "true"
print(f"UC_CATALOG: {UC_CATALOG}")
print(f"UC_SCHEMA: {UC_SCHEMA}")
print(f"UC_MODEL_NAME: {UC_MODEL_NAME_BASE}")
print(f"DEPLOY_ENDPOINT: {DEPLOY_ENDPOINT}")
Importar bibliotecas
Carregue as bibliotecas usadas em todo o notebook. O ambiente do AI Runtime v6 já inclui torch, transformers, trl e datasets, portanto, nenhuma instalação é necessária.
import torch
import pandas as pd
from datasets import load_dataset, Dataset
import transformers
from transformers import TrainingArguments, AutoTokenizer, AutoModelForCausalLM
from trl import SFTTrainer, SFTConfig
o passo 1: Carregar o modelo base e o tokenizador
Load the pre-trained Qwen3.5-0.8B-Base checkpoint from Hugging Face Hub.
- Architecture: Qwen3 (decoder-only transformer, ~0,8B parameters)
- Ponto de verificação "Base": nenhum ajuste fino de instruções aplicado ainda. Este é o modelo a ser ajustado abaixo.
- The model is moved to GPU immediately after loading for faster inference and treinamento
# Qwen3.5-0.8B-Base: ~0.8B parameter decoder-only model.
# "Base" = no instruction-tuning yet; this is the checkpoint fine-tuned below.
model_name = "Qwen/Qwen3.5-0.8B-Base"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForCausalLM.from_pretrained(model_name)
# Move all model weights to the GPU for faster inference and training.
model.to("cuda")
Configurar o tokenizer
Base checkpoints ship without a chat padrão. Define a minimal System / User / Assistant Jinja template so the tokenizer can correctly format prompts for single-turn and multi-turn conversations. Set pad_token = eos_token because the base vocabulary has no dedicated padding token.
# Base checkpoints ship without a chat template.
# Define a minimal System / User / Assistant Jinja template so the tokenizer
# can format both single-turn and multi-turn conversations correctly.
if not tokenizer.chat_template:
print("No chat template — applying default template")
tokenizer.chat_template = """{% for message in messages %}
{% if message['role'] == 'system' %}System: {{ message['content'] }}\n
{% elif message['role'] == 'user' %}User: {{ message['content'] }}\n
{% elif message['role'] == 'assistant' %}Assistant: {{ message['content'] }} <|endoftext|>
{% endif %}
{% endfor %}"""
# Set pad_token = eos_token because the base vocabulary has no dedicated pad token.
if not tokenizer.pad_token:
print("No pad token — using eos_token as pad_token")
tokenizer.pad_token = tokenizer.eos_token
print(tokenizer.chat_template[0:100])
print(tokenizer.pad_token)
Etapa 2: Inferência de linha de base (pré-SFT)
Run a quick sanity-check inference with the base (un-finetuned) model . The response here serves as a reference point. Compare it against the SFT model output in Step 5.
# Build a single-turn chat in OpenAI-style message format.
# The tokenizer's chat template will convert this list into a formatted prompt string.
messages = []
user_message = "Give me a one-sentence introduction to LLMs."
messages.append({"role": "user", "content": user_message})
messages
# Render the message list into a raw text string using the chat template.
# add_generation_prompt=True appends the "Assistant:" prefix to trigger generation.
# enable_thinking=False disables Qwen3's chain-of-thought reasoning mode.
prompt = tokenizer.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=True,
enable_thinking=False,
)
prompt
# Tokenize the prompt string and move tensors to the same device as the model.
inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
inputs
max_new_tokens = 100
with torch.no_grad(): # no gradient tracking needed during inference
outputs = model.generate(
**inputs, # pass tokenized prompt (input_ids + attention_mask)
max_new_tokens=max_new_tokens,
do_sample=False, # greedy decoding — deterministic output
pad_token_id=tokenizer.eos_token_id,
eos_token_id=tokenizer.eos_token_id,
)
outputs
# Slice off the prompt tokens; decode only the newly generated portion.
input_len = inputs["input_ids"].shape[1]
generated_ids = outputs[0][input_len:]
response = tokenizer.decode(generated_ids, skip_special_tokens=True).strip()
response
Funções auxiliares
Utilidades reutilizáveis definidas para este notebook:
generate_responses: formata um prompt com o padrão de chat e executa a decodificação gulosatest_model_with_questions: benchmarks a list of questions and prints model outputs side-by-sideload_model_and_tokenizer: loads model + tokenizer with optional GPU placement and padrão patchingdisplay_dataset: renderiza as primeiras 3 linhas de um dataset no formato de chat como uma tabela legível
def generate_responses(model, tokenizer, user_message, system_message=None,
max_new_tokens=100):
# Format chat using tokenizer's chat template
messages = []
if system_message:
messages.append({"role": "system", "content": system_message})
# Assume the data are all single-turn conversations
messages.append({"role": "user", "content": user_message})
prompt = tokenizer.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=True,
enable_thinking=False,
)
inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
# Recommended to use vllm, sglang or TensorRT
with torch.no_grad():
outputs = model.generate(
**inputs,
max_new_tokens=max_new_tokens,
do_sample=False,
pad_token_id=tokenizer.eos_token_id,
eos_token_id=tokenizer.eos_token_id,
)
input_len = inputs["input_ids"].shape[1]
generated_ids = outputs[0][input_len:]
response = tokenizer.decode(generated_ids, skip_special_tokens=True).strip()
return response
def test_model_with_questions(model, tokenizer, questions,
system_message=None, title="Model Output"):
print(f"\n=== {title} ===")
for i, question in enumerate(questions, 1):
response = generate_responses(model, tokenizer, question,
system_message)
print(f"\nModel Input {i}:\n{question}\nModel Output {i}:\n{response}\n")
def load_model_and_tokenizer(model_name, use_gpu = False):
# Load base model and tokenizer
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForCausalLM.from_pretrained(model_name)
if use_gpu:
model.to("cuda")
if not tokenizer.chat_template:
tokenizer.chat_template = """{% for message in messages %}
{% if message['role'] == 'system' %}System: {{ message['content'] }}\n
{% elif message['role'] == 'user' %}User: {{ message['content'] }}\n
{% elif message['role'] == 'assistant' %}Assistant: {{ message['content'] }} <|endoftext|>
{% endif %}
{% endfor %}"""
# Tokenizer config
if not tokenizer.pad_token:
tokenizer.pad_token = tokenizer.eos_token
return model, tokenizer
def display_dataset(dataset):
# Visualize the dataset
rows = []
for i in range(3):
example = dataset[i]
user_msg = next(m['content'] for m in example['messages']
if m['role'] == 'user')
assistant_msg = next(m['content'] for m in example['messages']
if m['role'] == 'assistant')
rows.append({
'User Prompt': user_msg,
'Assistant Response': assistant_msg
})
# Display as table
df = pd.DataFrame(rows)
pd.set_option('display.max_colwidth', None) # Avoid truncating long strings
display(df)
O passo 3: carregar o dataset de treinamento
Load banghua/DL-SFT-Dataset from the Hugging Face Hub, an instruction-following dataset from the DeepLearning.AI SFT course. Each example is a messages list with user and assistant turns.
Este exemplo usa um subconjunto de 100 exemplos para manter o tempo de treinamento curto. Aumente o tamanho do subconjunto para o ajuste fino real.
train_dataset = load_dataset("banghua/DL-SFT-Dataset")['train']
train_dataset=train_dataset.select(range(100))
display_dataset(train_dataset)
Step 4: SFT fine-tuning
Use o SFTTrainer do TRL para executar o Supervised Fine-Tuning . Escolhas de key hiperparâmetros para esta demonstração:
Parâmetro | Valor | Notas |
|---|---|---|
|
| Ponto de partida padrão para SFT em modelos pequenos |
|
| Passagem única para demonstração; aumente para o treinamento real |
|
| Tune with |
|
| Tamanho de lote efetivo = 1 × 8 = 8 |
|
| Desativado por velocidade; ative para reduzir a VRAM em modelos maiores |
# SFTConfig is a superset of HuggingFace TrainingArguments with SFT-specific defaults.
sft_config = SFTConfig(
# --- Training hyperparameters ---
learning_rate=8e-5, # standard starting point for SFT on small models
num_train_epochs=1, # single pass for demo; increase for real training
per_device_train_batch_size=1, # fits H100 VRAM; tune together with gradient_accumulation_steps
gradient_accumulation_steps=8, # effective batch size = 1 × 8 = 8
gradient_checkpointing=False, # disable for speed; enable to reduce VRAM on larger models
logging_steps=2,
# --- Logging ---
report_to=[], # disable W&B / MLflow / etc.
logging_strategy="steps",
logging_first_step=True,
# --- Checkpointing ---
output_dir="./checkpoints/tiny-finetune-exp1",
run_name="tiny-finetune-exp1-run", # must differ from output_dir to avoid W&B conflicts
save_strategy="no", # set to "epoch" to persist a final checkpoint
save_total_limit=1
)
# SFTTrainer handles dataset formatting, tokenization, and response-label masking
# automatically based on the tokenizer's chat template.
sft_trainer = SFTTrainer(
model=model,
args=sft_config,
train_dataset=train_dataset,
processing_class=tokenizer # replaces the deprecated tokenizer= arg in TRL 0.12+
)
# Run training — loss should decrease over the single epoch on this 100-example subset.
sft_trainer.train()
Etapa 5: Avaliação pós-SFT
Compare the fine-tuned model's responses on the same questions used in passo 2. Look for improved formatting and instruction-following style that reflects the treinamento data distribution.
questions = [
"Calculate 1+1-1",
"What's the difference between thread and process?"
]
test_model_with_questions(sft_trainer.model, tokenizer, questions,
title="Fine-tuned model output")
Etapa 6: Disponibilizar o modelo aperfeiçoado com o vLLM
Com o treinamento concluído, implante o modelo ajustado atrás de um endpoint de servindo modelo Custom Foundation Model do Databricks que executa um servidor compatível com vLLM OpenAI.
Um detalhe importante: Qwen/Qwen3.5-0.8B-Base é realmente um modelo multimodal , e AutoModelForCausalLM acima carregou apenas sua espinha dorsal de texto (Qwen3_5ForCausalLM). O vLLM (ambiente de IA v6) pode fornecer essa espinha dorsal de texto nativamente, mas alguns ajustes são necessários porque é a metade de texto de um modelo de visão-linguagem:
- Salve os pesos com ajuste fino com o
Qwen3_5Configcomposto (aquele que carregavision_config) mais os arquivos originais do processor do modelo, caso contrário, o processor do vLLM rejeitará o ponto de verificação. - At launch, tell vLLM there are zero images/videos (
--limit-mm-per-prompt) so it never touches the (absent) vision tower, run the linear-attention (GDN) kernels through Triton (--gdn-prefill-backend triton) so nothing has to JIT-compile withninja/nvcc, and route sampling through native torch (VLLM_USE_FLASHINFER_SAMPLER=0).
Tudo abaixo é executado na mesma sessão do ambiente Serverless GPU (H100) + AI v6.
Salvar o modelo com ajuste fino para serviços
O treinador mantém o modelo apenas na memória (save_strategy="no"), portanto, persista-o antes de reiniciar o Python:
save_pretrainedos pesos de texto ajustados (chamadosmodel.*).- Salvar o processador original do modelo (
preprocessor_config.json, etc.). A configuração composta declara um componente de visão, portanto, o vLLM insiste nisso. Em seguida, salve novamente o novo tokenizador por cima para que o padrão de conversa personalizado seja aplicado. - Sobrescreva a configuração de texto simples com o
Qwen3_5Configcomposto , comarchitecturespin emQwen3_5ForCausalLMpara que o vLLM carregue o modelo de texto (e não o modelo VL completo).
Execute isto enquanto sft_trainer, tokenizer e model_name ainda estiverem no escopo.
import os, tempfile
from transformers import AutoConfig, AutoProcessor
# Local-disk working dir. Use a fixed path (not a random tmpdir) so it survives %restart_python below;
# ARTIFACTS_PATH is a relative basename because the vLLM entrypoint's --model must match it both here
# and inside the packaged model's artifacts/ dir at serving time.
WORKDIR = os.path.join(tempfile.gettempdir(), "sft_serve")
ARTIFACTS_PATH = "qwen3_sft"
SAVE_DIR = os.path.join(WORKDIR, ARTIFACTS_PATH)
os.makedirs(SAVE_DIR, exist_ok=True)
# 1. Fine-tuned text backbone.
sft_trainer.model.save_pretrained(SAVE_DIR)
# 2. Original processor files, then the new tokenizer (with the custom chat template) on top.
try:
AutoProcessor.from_pretrained(model_name).save_pretrained(SAVE_DIR)
except Exception as e:
print("processor save skipped:", e)
tokenizer.save_pretrained(SAVE_DIR)
# 3. Composite Qwen3_5Config (text_config + vision_config), arch pinned to the text model.
cfg = AutoConfig.from_pretrained(model_name)
cfg.architectures = ["Qwen3_5ForCausalLM"]
cfg.save_pretrained(SAVE_DIR)
print("saved ->", SAVE_DIR)
print("config architectures:", cfg.architectures, "| model_type:", cfg.model_type,
"| has vision_config:", hasattr(cfg, "vision_config"))
Reiniciar o Python para liberar a H100
Nada precisa ser instalado: o AI env v6 já inclui o flashinfer e seus kernels flashinfer-cubin pré-compilados, o prefill do GDN (linear-attention) é executado no Triton e a amostragem é executada no torch nativo (ambos definidos nas flags de lançamento abaixo), portanto, nenhum JIT de kernel é compilado na Startup.
The one thing needed is to release the GPU memory the trainer is holding so vLLM can claim it. %restart_python does that; the saved checkpoint on local disk survives the restart (same driver node).
%restart_python
Configuração de disponibilização
A parte superior da próxima célula contém os valores definidos para o seu workspace: os nomes de modelo/endpoint e o dimensionamento do endpoint. O resto é a fiação interna que você pode deixar como está. No mínimo, defina UC_MODEL_NAME como um caminho do Unity Catalog em que você possa gravar antes de executar as células de registro/implantação.
from databricks.sdk.service.serving import ServingModelWorkloadType
# Re-read the widgets: %restart_python reset the Python process, but the widget
# values set at the top of the notebook persist and can be read again here.
UC_CATALOG = dbutils.widgets.get("uc_catalog")
UC_SCHEMA = dbutils.widgets.get("uc_schema")
UC_MODEL_NAME_BASE = dbutils.widgets.get("uc_model_name")
DEPLOY_ENDPOINT = dbutils.widgets.get("deploy_endpoint").lower() == "true"
# --- Model / endpoint names ---
UC_MODEL_NAME = f"{UC_CATALOG}.{UC_SCHEMA}.{UC_MODEL_NAME_BASE}" # Unity Catalog catalog.schema.model
ENDPOINT_NAME = f"{UC_MODEL_NAME_BASE}-endpoint" # serving endpoint name; unique per workspace
SERVED_MODEL_NAME = UC_MODEL_NAME_BASE # name vLLM exposes the model under
# --- Endpoint sizing (adjust if needed) ---
# --gdn-prefill-backend triton (see the entrypoint) JITs the GDN kernels for whatever GPU the pod
# lands on, so this is not pinned to Hopper. GPU_MEDIUM fits the 0.8B model; verify at deploy.
WORKLOAD_TYPE = ServingModelWorkloadType.GPU_MEDIUM
WORKLOAD_SIZE = "Small"
SCALE_TO_ZERO_ENABLED = True
# --- Internal wiring: leave as-is ---
import os, tempfile
# Local-disk working dir; must match the save cell (survives %restart_python; same driver node).
WORKDIR = os.path.join(tempfile.gettempdir(), "sft_serve")
ARTIFACTS_PATH = "qwen3_sft" # relative basename; entrypoint --model resolves to ./qwen3_sft
os.chdir(WORKDIR) # so `--model qwen3_sft` works locally and matches artifacts/ at serving
# Allowlisted ports for serverless GPU notebooks are 3000-3999. Model Serving requires 8080.
LOCAL_PORT = 3080
SERVING_PORT = 8080
# vLLM tuning.
DTYPE = "float16" # model is bf16-native; this matches what shipped
MAX_MODEL_LEN = 8192 # keep <= the model's max_position_embeddings (config.json)
GPU_MEMORY_UTILIZATION = 0.85
Testar o modelo ajustado localmente com o vLLM
Inicie um servidor vLLM OpenAI com o ponto de verificação salvo e execute um teste básico antes de gastar ~40 minutos na implantação de um endpoint. Um ponto de verificação incorreto ou uma flag causa falha aqui em segundos.
O comando entrypoint() é definido uma vez e reutilizado tanto para o teste local (LOCAL_PORT) quanto para o endpoint de disponibilização (SERVING_PORT); a exata mesma string é armazenada nos metadados do modelo e executada novamente dentro do contêiner de disponibilização.
def entrypoint(port: int) -> str:
args = [
"VLLM_USE_FLASHINFER_SAMPLER=0", # native torch sampling; avoids the flashinfer sampler JIT
"python", "-u", "-m", "vllm.entrypoints.openai.api_server",
"--model", ARTIFACTS_PATH,
"--served-model-name", SERVED_MODEL_NAME,
"--host", "0.0.0.0",
"--port", str(port),
"--dtype", DTYPE,
"--max-model-len", str(MAX_MODEL_LEN),
"--gpu-memory-utilization", str(GPU_MEMORY_UTILIZATION),
# Run GDN (linear-attention) prefill through Triton (its own bundled compiler) instead of
# flashinfer's JIT kernel, so no ninja/nvcc is needed here or in the serving pod.
"--gdn-prefill-backend", "triton",
# Text-only serving of a multimodal-config model: allow zero images/videos so vLLM never
# profiles or exercises the vision tower (absent from the text weights).
"--limit-mm-per-prompt", "'{\"image\": 0, \"video\": 0}'",
]
return " ".join(args)
# Start the vLLM server in the background; logs stream to process.log.
import subprocess
log = open("process.log", "w")
subprocess.Popen(
["bash", "-lc", entrypoint(LOCAL_PORT)],
stdout=log,
stderr=subprocess.STDOUT,
text=True,
start_new_session=True,
)
%sh
# Tail logs until vLLM is ready. If this hangs, vLLM startup probably hit an error (read process.log).
tail -f process.log | sed -u '/Application startup complete/q'
# Smoke test (sync): the endpoint speaks the OpenAI chat schema at /invocations.
import requests
resp = requests.post(f"http://localhost:{LOCAL_PORT}/invocations", json={"messages": [{"role": "user", "content": "Hello"}]})
resp.json()["choices"][0]["message"]["content"]
# Smoke test (streaming): vLLM streams completions as Server-Sent Events — one JSON chunk per
# `data: ` line, terminated by `data: [DONE]`.
import requests
import json
resp = requests.post(
f"http://localhost:{LOCAL_PORT}/invocations",
json={"messages": [{"role": "user", "content": "Tell me a story that is about 300 words!"}], "stream": True},
stream=True,
)
for line in resp.iter_lines():
if not line:
continue
if line == b"data: [DONE]":
break
if line.startswith(b"data: "):
data = json.loads(line[6:])
delta = data["choices"][0].get("delta", {})
if "content" in delta:
print(delta["content"], end="", flush=True)
%sh
# Stop the local server before logging/registering (the endpoint runs its own copy).
pkill -f vllm.entrypoints.openai.api_server
Etapa 7: Fazer o log e registrar o modelo
Registre uma execução (ChatModel) do MLflow cujos metadados contenham task = llm/v1/chat e o comando vLLM entrypoint. Serving executa esse entrypoint, e não python_model.predict, portanto o corpo da classe é apenas um placeholder obrigatório.
O registro com env_pack="databricks_model_serving" produz os artefatos do Serverless Optimized Deployment (SOD) (pesos + ambiente empacotado) exigidos pelo Custom LLM Serving. O registro deve ser feito a partir deste runtime de GPU serverless para garantir que as dependências corretas da GPU sejam empacotadas.
import os
import mlflow
from mlflow.pyfunc.model import ChatModel, ChatCompletionResponse
# Required placeholder. Serving runs the entrypoint, not python_model.predict.
class LLMModel(ChatModel):
def predict(self, context, messages, params):
return ChatCompletionResponse.from_dict({"choices": []})
# You must log and register from a Serverless GPU runtime, otherwise the model is packaged
# with CPU deps and the GPU serving endpoint fails to start.
if not os.environ.get("DATABRICKS_ACCELERATOR"):
raise RuntimeError(
"This model MUST be logged+registered from a serverless GPU runtime, otherwise the correct dependencies will not be packaged for serving."
)
model_info = mlflow.pyfunc.log_model(
name=SERVED_MODEL_NAME,
python_model=LLMModel(),
artifacts={
"model_dir": ARTIFACTS_PATH,
},
metadata={
"task": "llm/v1/chat",
"entrypoint": entrypoint(SERVING_PORT),
},
# Pin whatever mlflow ships in AI env v6 (rather than a hardcoded version).
extra_pip_requirements=[f"mlflow=={mlflow.__version__}"],
)
model_info.model_uri
import mlflow
# env_pack is required. Custom LLM Serving depends on Serverless Optimized Deployments (SOD).
# The endpoint will not work without it.
# https://docs.databricks.com/aws/en/machine-learning/model-serving/serverless-optimized-deployments
model_version = mlflow.register_model(model_info.model_uri, UC_MODEL_NAME, env_pack="databricks_model_serving")
Passo 8: Criar o endpoint de serviço
Crie um endpoint de modelo de fundação personalizado que atenda à versão registrada. create_and_wait bloqueia até que o endpoint esteja pronto (até 40 minutos para a primeira implantação; o contêiner faz o download dos artefatos SOD e inicializa o vLLM).
Implantar o endpoint (opcional)
Os passos restantes implantam um endpoint de servindo modelo gerenciado. Eles exigem a servindo modelo Custom Foundation Model baseada em entrypoint (implantação otimizada para Serverless). Se deploy_endpoint for false, o Notebook para aqui.
if not DEPLOY_ENDPOINT:
dbutils.notebook.exit("deploy_endpoint=false: skipping managed serving endpoint deployment")
from databricks.sdk import WorkspaceClient
from datetime import timedelta
from databricks.sdk.service.serving import EndpointCoreConfigInput, ServedEntityInput
served_entities = [
ServedEntityInput(
entity_name=UC_MODEL_NAME,
entity_version=str(model_version.version),
workload_type=WORKLOAD_TYPE,
workload_size=WORKLOAD_SIZE,
scale_to_zero_enabled=SCALE_TO_ZERO_ENABLED,
)
]
w = WorkspaceClient()
# Create the endpoint, or update it in place if an endpoint of this name already exists,
# so the notebook is safe to re-run. The first deploy can take up to ~40 minutes while the
# container downloads the Serverless Optimized Deployment artifacts and boots vLLM.
existing = {e.name for e in w.serving_endpoints.list()}
if ENDPOINT_NAME in existing:
print(f"Updating existing endpoint: {ENDPOINT_NAME}")
w.serving_endpoints.update_config_and_wait(
name=ENDPOINT_NAME, served_entities=served_entities, timeout=timedelta(minutes=40)
)
else:
print(f"Creating endpoint: {ENDPOINT_NAME}")
config = EndpointCoreConfigInput(name=ENDPOINT_NAME, served_entities=served_entities)
w.serving_endpoints.create_and_wait(
name=ENDPOINT_NAME, config=config, timeout=timedelta(minutes=40)
)
Passo 9: query o Endpoint
Once the endpoint is ready, query it three ways: the Databricks SDK, the OpenAI client, and the OpenAI client with transmissão.
# Query using the Databricks SDK.
from databricks.sdk import WorkspaceClient
from databricks.sdk.service.serving import ChatMessage, ChatMessageRole
w = WorkspaceClient()
resp = w.serving_endpoints.query(
name=ENDPOINT_NAME,
messages=[ChatMessage(role=ChatMessageRole.USER, content="Hi, what model are you?")],
)
print(resp.choices[0].message.content)
# Query using the OpenAI client.
from openai import OpenAI
DATABRICKS_HOST = dbutils.notebook.entry_point.getDbutils().notebook().getContext().apiUrl().get()
DATABRICKS_TOKEN = dbutils.notebook.entry_point.getDbutils().notebook().getContext().apiToken().get()
client = OpenAI(
api_key=DATABRICKS_TOKEN,
base_url=f"{DATABRICKS_HOST}/serving-endpoints",
)
response = client.chat.completions.create(
model=ENDPOINT_NAME,
messages=[
{"role": "user", "content": "Hello"},
],
)
print(response.choices[0].message.content)
# Query using the OpenAI client (streaming).
stream = client.chat.completions.create(
model=ENDPOINT_NAME,
messages=[
{"role": "user", "content": "Hello, tell me a 200 word story"},
],
stream=True,
)
for event in stream:
delta = event.choices[0].delta
print(delta.content, end="|")
Passos seguintes
Agora que você ajustou, registrou e serviu seu modelo, você pode:
- Query the endpoint from your applications : Query foundation models and external models
- Saiba mais sobre a disponibilização personalizada de LLM : Disponibilizar LLMs personalizados
- Otimizar o uso de GPU serverless : Melhores práticas para o AI Runtime
- Solucionar problemas : Solucionar problemas no AI Runtime