Files
homelab/k8s/apps/llm-serving/clef-serve.yaml
T

126 lines
4.3 KiB
YAML
Raw Normal View History

# clef-flash: a decision model, not a chat model.
#
# It reads a `state` plus a schema of typed `questions` and returns one probability
# per allowed option in a single prefill pass. There is no decoding, no sampling and
# no free-form text. The public route is /v1/systemone, and its request and response
# shapes are Clef's own: no `messages`, no `choices`.
#
# Why not SGLang: Clef support landed in SGLang v0.5.22, which upstream publishes only
# on CUDA 13. CUDA 13 dropped Volta kernels and every card here is a Tesla V100
# (compute capability 7.0), so SGLang aborts with "no kernel image is available for
# execution on the device". SGLang also states sm75 as its floor regardless.
#
# So this runs the implementation the model ships: joint_schema_model.py, which has
# load_release_model, encode_record, collate_records and systemone. The weights are
# 17.75 GiB of BF16, and fp16 costs the same bytes per parameter. On one 32 GiB card
# that leaves room for a 32k state, which is why this is one GPU and not two: with
# device_map over two cards the embedding table and the tensors indexing into it land
# on different devices, and the forward pass dies with
# "indices should be either on cpu or on the same device as the indexed tensor".
#
# Known gap: the image cannot be pushed to the registry. Cloudflare fronts
# forgejo.riotpiao.com and answers 413 Payload Too Large for any layer above 100 MB,
# and the torch base alone is 7.58 GB. So this mounts app.py from a ConfigMap and pip
# installs on every start. A cold start therefore needs PyPI reachable, and takes
# about four minutes. Build the image in-cluster, or unproxy the registry host, before
# treating this as production.
apiVersion: apps/v1
kind: Deployment
metadata:
name: clef-serve
namespace: llm-serving
labels:
app: clef-serve
app.kubernetes.io/part-of: llm-serving
spec:
replicas: 1
# Recreate, not RollingUpdate. Every GPU card is allocated, so a rolling update leaves
# the new pod Pending forever waiting for a card the old pod still holds.
strategy:
type: Recreate
selector:
matchLabels:
app: clef-serve
template:
metadata:
labels:
app: clef-serve
app.kubernetes.io/part-of: llm-serving
spec:
runtimeClassName: nvidia
nodeSelector:
kubernetes.io/hostname: worker-1
containers:
- name: clef
image: pytorch/pytorch:2.8.0-cuda12.8-cudnn9-runtime
command: ["/bin/bash","-lc"]
args:
- |
set -e
pip install --no-cache-dir transformers==5.10.2 accelerate fastapi 'uvicorn[standard]' pillow safetensors sentencepiece protobuf
exec uvicorn app:create_app --factory --host 0.0.0.0 --port 8080
workingDir: /app
env:
- name: PYTHONUNBUFFERED
value: "1"
- name: MODEL_PATH
value: /mnt/models/clef-flash
# V100 has no bf16, and the release weights are bf16. fp16 is the same width.
- name: DTYPE
value: float16
# The state is truncated at this length without warning, so raise it only with
# spare VRAM: 17.75 GiB of weights plus 128 KiB of KV per token.
- name: MAX_LEN
value: "32768"
ports:
- containerPort: 8080
# A cold start pip-installs and loads 17.75 GiB, measured at about four minutes.
startupProbe:
httpGet:
path: /health
port: 8080
failureThreshold: 120
periodSeconds: 10
readinessProbe:
httpGet:
path: /health
port: 8080
periodSeconds: 10
resources:
requests:
cpu: "4"
memory: 12Gi
nvidia.com/gpu: "1"
limits:
cpu: "8"
memory: 40Gi
nvidia.com/gpu: "1"
volumeMounts:
- name: app
mountPath: /app
- name: models
mountPath: /mnt/models
readOnly: true
volumes:
- name: app
configMap:
name: clef-serve-app
- name: models
persistentVolumeClaim:
claimName: llm-models
---
apiVersion: v1
kind: Service
metadata:
name: clef-serve
namespace: llm-serving
labels:
app.kubernetes.io/part-of: llm-serving
spec:
selector:
app: clef-serve
ports:
- name: http
port: 80
targetPort: 8080