Lab 04-01: Server e web UI¶
Obiettivo¶
Avviare llama serve con un modello piccolo, leggere i log di avvio, usare la web UI dal browser
(allegati, branching, schema JSON) e interrogare con curl gli endpoint principali: /health, /props,
/v1/models, /v1/chat/completions (normale e streaming), /tokenize, /apply-template, /slots, /metrics.
Prerequisiti¶
llamainstallato e funzionante (modulo 01),curlepython3(perpython3 -m json.tool) ojq.- Il modello del modulo 02 già in cache (altrimenti il primo avvio lo scarica: 785 MB).
- Ubuntu con binario dell'installer:
export SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt.
Materiale¶
| Cosa | Dove | Dimensione |
|---|---|---|
ggml-org/Qwen3.5-0.8B-GGUF (file Qwen3.5-0.8B-Q8_0.gguf, licenza Apache 2.0) |
Hugging Face, via -hf |
785 MiB |
| Una foto o un PDF (solo se usi un modello multimodale, variante finale) | tua | – |
Procedura¶
-
Avvia il server in un terminale che lascerai aperto. Usiamo
-c 2048 -np 2apposta per vedere la divisione del contesto,--metricsper l'endpoint Prometheus e un alias:llama serve -hf ggml-org/Qwen3.5-0.8B-GGUF --host 127.0.0.1 --port 8080 -c 2048 -np 2 --metrics -a qwen-0.8bOutput atteso (le righe importanti):
srv load_model: initializing, n_slots = 2, n_ctx_slot = 1024, kv_unified = 'false' srv llama_server: model loaded srv llama_server: listening on http://127.0.0.1:8080Annota
n_ctx_slot: hai chiesto 2048 di contesto e 2 slot, ogni richiesta ne avrà 1024. -
Controlla che sia pronto da un secondo terminale:
curl -s http://127.0.0.1:8080/health ; echo curl -s http://127.0.0.1:8080/v1/models | python3 -m json.tool | head -30Atteso:
{"status":"ok"}e un elencodatacon un solo modello,"id": "qwen-0.8b"(l'alias),owned_by: llamacppe inmetai dati del modello (n_ctx_train,n_params,size). -
Leggi le proprietà del server:
curl -s http://127.0.0.1:8080/props | python3 -c " import json,sys; d=json.load(sys.stdin) print('slot:', d['total_slots'], '| n_ctx per slot:', d['default_generation_settings']['n_ctx']) print('modalità:', d['modalities'], '| build:', d['build_info']) print('template supporta tool:', d['chat_template_caps']['supports_tools']) p=d['default_generation_settings']['params']; print({k:p[k] for k in ['temperature','top_k','top_p','min_p','repeat_penalty']})"Atteso:
slot: 2 | n_ctx per slot: 1024,modalities {'vision': False, 'video': False, 'audio': False},build: b11046-60081bb2b, i sampler di default (temperature 0.8, top_k 40, top_p 0.95, min_p 0.05, repeat_penalty 1.0). -
Prima chat via API. Spegniamo il thinking del modello con
chat_template_kwargs:curl -s http://127.0.0.1:8080/v1/chat/completions -H "Content-Type: application/json" -d '{ "messages": [{"role": "user", "content": "Qual è la capitale d'\''Italia? Rispondi in una frase."}], "max_tokens": 40, "temperature": 0, "chat_template_kwargs": {"enable_thinking": false}}' | python3 -m json.toolAtteso:
"content": "Roma è la capitale d'Italia.",finish_reason: stop,usagecon 27 token di prompt e ~9 di completamento, e il bloccotimingsconprompt_per_second,predicted_per_second,cache_n: 0. -
Ripeti la stessa richiesta e guarda
timings.cache_neprompt_n: ora il prompt è già in cache (cache_nvicino al totale,prompt_npiccolo,prompt_msmolto più basso): ad esempiocache_n 0 → 33,prompt_ms 1259 → 227. -
Streaming. Aggiungi
"stream": truee usacurl -N:curl -sN http://127.0.0.1:8080/v1/chat/completions -H "Content-Type: application/json" -d '{ "messages": [{"role": "user", "content": "Conta da 1 a 5."}], "max_tokens": 30, "temperature": 0, "stream": true, "chat_template_kwargs": {"enable_thinking": false}}'Atteso: righe
data: {"choices":[{"delta":{"content":"…"}}],…,"object":"chat.completion.chunk"}, una per token, un ultimo chunk con"finish_reason":"stop"etimings, poidata: [DONE]. -
Token e template:
curl -s http://127.0.0.1:8080/tokenize -H "Content-Type: application/json" -d '{"content":"Ciao mondo!","with_pieces":true}'; echo curl -s http://127.0.0.1:8080/apply-template -H "Content-Type: application/json" \ -d '{"messages":[{"role":"system","content":"Sei un assistente."},{"role":"user","content":"Ciao"}]}'; echoAtteso: 4 token (
C,iao,mondo,!) e il prompt ChatML<|im_start|>system\n…<|im_start|>assistant\n<think>\n. Quel<think>finale è il motivo per cui il modello ragiona se non glielo vieti. -
Slot e metriche:
curl -s http://127.0.0.1:8080/slots | python3 -m json.tool | grep -E '"id"|n_ctx|is_processing' curl -s http://127.0.0.1:8080/metrics | grep -E "^llamacpp:(prompt_tokens_total|tokens_predicted_total|requests_processing)"Atteso: due slot con
n_ctx: 1024eis_processing: false; i contatori Prometheus con i token delle richieste fatte finora. -
Web UI. Apri
http://127.0.0.1:8080nel browser e:- fai una domanda e osserva lo streaming e il blocco "reasoning" ripiegabile (qui il thinking è attivo: la UI ha la sua impostazione per mostrarlo/nasconderlo e per i parametri di campionamento);
- modifica un messaggio precedente: la conversazione si biforca, con le frecce passi da un ramo all'altro;
-
Settings → Developer → Custom JSONe incolla:{"json_schema": {"type": "object", "properties": {"sentimento": {"type": "string", "enum": ["positivo", "neutro", "negativo"]}, "riassunto": {"type": "string", "maxLength": 120}}, "required": ["sentimento", "riassunto"]}}poi scrivi «Il servizio era lento ma il personale gentilissimo»: la risposta è un JSON che rispetta lo schema; - esporta la conversazione in JSON dal menu, poi rimuovi lo schema per non condizionare le prove successive.
-
Ferma il server con Ctrl+C nel primo terminale (o
pkill -f "llama serve").
Verifica del risultato¶
- Nel log hai letto
n_slots = 2, n_ctx_slot = 1024e sai spiegare perché non 2048. -
/health→{"status":"ok"};/v1/modelsriporta l'aliasqwen-0.8b. - La risposta di
/v1/chat/completionscontienetimingse alla seconda chiamatacache_n > 0. - Hai visto lo stream SSE terminare con
data: [DONE]. - Nella web UI hai creato un ramo e ottenuto un JSON conforme allo schema.
Domande e varianti¶
- Avvia con
-np 1e-c 2048: quanto valen_ctx_slot? E con-np 2 --kv-unified? (guarda il log e/props) - Avvia con
--api-key segreta123: cosa risponde/v1/modelssenza header? E/health? (401 Invalid API Key;/healthresta200) - Avvia con
--no-webui:GET /risponde404ma l'API funziona. Quando ha senso? - Manda una richiesta con
"max_tokens": 10senza spegnere il thinking: cosa contienecontent? Cosa vedi inreasoning_content? - Variante multimodale (se hai ≥ 8 GB liberi):
llama serve -hf ggml-org/gemma-3-4b-it-qat-GGUFe allega un'immagine nella UI;/propsora riporta"vision": true. - Variante
--ui-config '{"theme":"dark"}': la UI parte in tema scuro.
Consegna¶
Un file lab-04-01.md con: le tre righe di log dell'avvio; l'output di /props filtrato (punto 3); le due risposte
del punto 4-5 con cache_n evidenziato; uno screenshot (o l'export JSON) della conversazione con il ramo e la
risposta JSON conforme allo schema; le risposte alle prime tre domande.