Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e30d833624 | ||
|
|
35a8a8d975 | ||
|
|
a003df0ef8 | ||
|
|
f0e48b226a | ||
|
|
669ef1c5ee | ||
|
|
68865e15c5 |
@@ -44,12 +44,37 @@ spec:
|
||||
- Qwen/Qwen3-Embedding-0.6B-GGUF:Q8_0
|
||||
# Serves POST /v1/embeddings.
|
||||
- --embeddings
|
||||
# A short, stable id for consumers. Without it the model is
|
||||
# advertised as the whole --hf-repo string, which would end up
|
||||
# verbatim in Paperless's config and break on the next model swap.
|
||||
# One name only: llama-server keeps aliases in a sorted set and
|
||||
# reports the first as the id, so a second name can steal it.
|
||||
- --alias
|
||||
- qwen3-embedding-0.6b
|
||||
# Qwen3-Embedding is trained for last-token pooling. Left
|
||||
# unspecified llama-server takes the GGUF's default, which is not
|
||||
# visible from outside and would silently produce vectors the model
|
||||
# was not trained to emit. Set before anything is indexed: changing
|
||||
# pooling later invalidates every stored embedding.
|
||||
- --pooling
|
||||
- last
|
||||
- --n-gpu-layers
|
||||
- "99"
|
||||
- --threads
|
||||
- "2"
|
||||
# Paperless chunks documents at 1024 tokens. The defaults are
|
||||
# batch 2048 / ubatch 512, and with --embeddings llama-server
|
||||
# clamps both down to 512 rather than run them mismatched — below
|
||||
# the chunk size. Matching both to 1024 fits a whole chunk in one
|
||||
# pass; 2048 is deliberately avoided, since that is where this
|
||||
# Vulkan stack fails with ErrorDeviceLost.
|
||||
- --batch-size
|
||||
- "1024"
|
||||
- --ubatch-size
|
||||
- "1024"
|
||||
# Room for a full chunk plus overhead rather than exactly one.
|
||||
- --ctx-size
|
||||
- "2048"
|
||||
- "4096"
|
||||
# No browser ever talks to this Service, so the WebUI is dead weight
|
||||
# and one more unauthenticated surface.
|
||||
- --no-webui
|
||||
@@ -66,14 +91,24 @@ spec:
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
# The weights are only ~600 MiB, but that is not what sets the
|
||||
# footprint: four slots at 4096 context with a 1024-token micro-batch,
|
||||
# and Vulkan allocating the iGPU's working set from system RAM where
|
||||
# it counts against this cgroup.
|
||||
#
|
||||
# 4Gi was sized for the earlier 2048 context and 512 micro-batch. Both
|
||||
# were raised to match Paperless's chunking without raising this, and
|
||||
# the pod then survived interactive use and was OOMKilled (137) partway
|
||||
# through the first sustained run — roughly 300 documents in. Indexing
|
||||
# the archive would have hit it on day one.
|
||||
resources:
|
||||
requests:
|
||||
cpu: 250m
|
||||
memory: 2Gi
|
||||
memory: 4Gi
|
||||
gpu.intel.com/i915: "1"
|
||||
limits:
|
||||
cpu: "2"
|
||||
memory: 4Gi
|
||||
memory: 10Gi
|
||||
gpu.intel.com/i915: "1"
|
||||
# ~600 MiB rather than ~5 GiB, so a much shorter budget than the chat
|
||||
# model needs, but still sized for a cold download.
|
||||
|
||||
@@ -57,6 +57,20 @@ spec:
|
||||
protocol: UDP
|
||||
- port: "53"
|
||||
protocol: TCP
|
||||
# The cluster's own LLM, for AI suggestions and RAG embeddings. Scoped to
|
||||
# the two pods and the one port rather than the ai namespace as a whole, so
|
||||
# this grant cannot widen if something else is ever placed there.
|
||||
- toEndpoints:
|
||||
- matchLabels:
|
||||
"k8s:io.kubernetes.pod.namespace": ai
|
||||
app.kubernetes.io/name: llama-server
|
||||
- matchLabels:
|
||||
"k8s:io.kubernetes.pod.namespace": ai
|
||||
app.kubernetes.io/name: llama-embed
|
||||
toPorts:
|
||||
- ports:
|
||||
- port: "8080"
|
||||
protocol: TCP
|
||||
# External HTTPS only — Tika model downloads, runtime image
|
||||
# fetches, etc. RFC1918 + ULA are excluded from `world` via the
|
||||
# CIDR-deny pattern. `world` covers public IPs / DNS-resolvable
|
||||
|
||||
@@ -46,6 +46,44 @@ data:
|
||||
PAPERLESS_TIKA_ENABLED: "1"
|
||||
PAPERLESS_TIKA_ENDPOINT: "http://tika:9998"
|
||||
PAPERLESS_TIKA_GOTENBERG_ENDPOINT: "http://gotenberg:3000"
|
||||
# AI suggestions against the cluster's own LLM (kubernetes/apps/llama-server).
|
||||
# Nothing leaves the cluster: both endpoints are ClusterIP Services, so
|
||||
# document text is never sent to a third party and there is no API key to
|
||||
# rotate — llama-server has no authentication, and the ai namespace's network
|
||||
# policy is what restricts who may reach it.
|
||||
#
|
||||
# CAVEAT, and it is a real one: paperless reads these from an
|
||||
# ApplicationConfiguration row in the database and falls back to the
|
||||
# environment only when that row is empty. A value set in the admin UI
|
||||
# therefore overrides this ConfigMap silently. Configure AI in ONE place —
|
||||
# here — and leave those fields untouched in the UI.
|
||||
PAPERLESS_AI_ENABLED: "true"
|
||||
PAPERLESS_AI_LLM_BACKEND: "openai-like"
|
||||
PAPERLESS_AI_LLM_ENDPOINT: "http://llama-server.ai.svc.cluster.local:8080/v1"
|
||||
# Must match --alias in kubernetes/apps/llama-server/deployment.yaml. The
|
||||
# server ignores the field on the wire, but paperless sends it and the value
|
||||
# is what appears in its logs.
|
||||
PAPERLESS_AI_LLM_MODEL: "qwen3.6-35b-a3b"
|
||||
# ClusterIP addresses are private, and paperless blocks non-public endpoints
|
||||
# unless this is set. Default is already true; explicit because the whole
|
||||
# integration silently stops working if it ever flips.
|
||||
PAPERLESS_AI_LLM_ALLOW_INTERNAL_ENDPOINTS: "true"
|
||||
# Under llama-server's own 16384, leaving room for the response.
|
||||
PAPERLESS_AI_LLM_CONTEXT_SIZE: "12288"
|
||||
# The 120s default is too tight at ~11 tok/s: a classification is roughly
|
||||
# 2-4k prompt tokens plus a second round trip for localisation.
|
||||
PAPERLESS_AI_LLM_REQUEST_TIMEOUT: "300"
|
||||
# Matched against Django's settings.LANGUAGES, so the region is required —
|
||||
# "de" alone does not resolve. Without it the model returns German tags but
|
||||
# English document types, which was reproducible against this endpoint.
|
||||
PAPERLESS_AI_LLM_OUTPUT_LANGUAGE: "de-de"
|
||||
# RAG embeddings from llama-embed. Its Qwen3-Embedding covers 100+ languages,
|
||||
# where paperless's default all-MiniLM-L6-v2 is documented as suited to
|
||||
# primarily English archives — the better fit for a German corpus.
|
||||
PAPERLESS_AI_LLM_EMBEDDING_BACKEND: "openai-like"
|
||||
PAPERLESS_AI_LLM_EMBEDDING_ENDPOINT: "http://llama-embed.ai.svc.cluster.local:8080/v1"
|
||||
PAPERLESS_AI_LLM_EMBEDDING_MODEL: "qwen3-embedding-0.6b"
|
||||
PAPERLESS_AI_LLM_EMBEDDING_CHUNK_SIZE: "1024"
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
@@ -58,6 +96,13 @@ metadata:
|
||||
# Reloader restarts paperless when paperless-secrets changes (e.g. after a
|
||||
# secret-key rotation). See kubernetes/infrastructure/openbao/rotation/paperless.yaml.
|
||||
secret.reloader.stakater.com/reload: paperless-secrets
|
||||
# And when paperless-config changes. The ConfigMap is consumed with
|
||||
# envFrom, and Kubernetes does not restart a pod when a ConfigMap it reads
|
||||
# that way is updated — the running container keeps the environment it
|
||||
# started with. Without this, a settings change applies cleanly in git and
|
||||
# in the cluster while the application never sees it, which is how the AI
|
||||
# settings landed with no effect at all.
|
||||
configmap.reloader.stakater.com/reload: paperless-config
|
||||
spec:
|
||||
replicas: 1
|
||||
strategy:
|
||||
|
||||
@@ -24,6 +24,16 @@ spec:
|
||||
- ports:
|
||||
- port: "8080"
|
||||
protocol: TCP
|
||||
# Paperless calls both models for AI suggestions and RAG embeddings. The
|
||||
# matching egress grant on its side is scoped to these two pods on :8080.
|
||||
- fromEndpoints:
|
||||
- matchLabels:
|
||||
"k8s:io.kubernetes.pod.namespace": paperless
|
||||
app.kubernetes.io/name: paperless
|
||||
toPorts:
|
||||
- ports:
|
||||
- port: "8080"
|
||||
protocol: TCP
|
||||
- fromEndpoints:
|
||||
- matchLabels:
|
||||
"k8s:io.kubernetes.pod.namespace": monitoring
|
||||
|
||||
Reference in New Issue
Block a user