Integridad del checkpoint
Laya descarga los pesos del modelo desde el Hugging Face Hub al cargarlos. Por defecto toma lo que apunte la revisión por defecto del repositorio, lo cual es cómodo y es lo que ya contiene una caché offline. Si prefieres fijar un commit revisado, o negarte a cargar un checkpoint cuyos bytes hayan cambiado, ambas cosas están disponibles y ambas son opcionales.
Nada de esto cambia lo que carga Laya hasta que lo pidas, así que añadir estas opciones a un despliegue
existente es seguro. Ambas son de nivel de biblioteca: los digests llegan al servidor HTTP a través de una
variable de entorno, y la fijación de revisión a través de LAYA_REVISION — consulta
Fijar una revisión en el servidor.
Relacionado: Docker para las variables de despliegue, laya.load y Agent,
y Router.
Fijar una revisión
Pasa revision a cualquier cargador. Acepta un SHA de commit, una rama o una etiqueta, y se reenvía al Hub.
import laya
agent = laya.load("convaiinnovations/laya", revision="<commit sha, branch, or tag>")
print(agent.revision) # what the download resolved to
Prefiere los SHAs revisados que se distribuyen con Laya a un literal propio: se actualizan con los checkpoints, así que esta forma no puede quedar obsoleta.
from laya import PINNED_REVISIONS
agent = laya.load(
"convaiinnovations/laya",
revision=PINNED_REVISIONS["convaiinnovations/laya"],
)
Router acepta el mismo revision, y revisions para fijar cada checkpoint por separado. Las claves de
PINNED_REVISIONS son los tres repositorios independientes, así que fija un Router con
standalone_repos=True:
router = laya.Router(standalone_repos=True, revisions={
"english": PINNED_REVISIONS["convaiinnovations/laya"],
"multilingual": PINNED_REVISIONS["convaiinnovations/laya-multilingual"],
"typed-decisions": PINNED_REVISIONS["convaiinnovations/laya-typed-decisions"],
})
Esto importa porque un Router por defecto carga los tres checkpoints desde el único repositorio bundle
(convaiinnovations/laya, con multilingual/ y typed-decisions/ como subcarpetas), y un SHA de commit
de laya-multilingual no existe en el repositorio bundle. Sin standalone_repos la fijación no se ignora
sin más — la carga falla. Si prefieres quedarte en el repositorio bundle, fíjalo con un solo revision=
para los tres en lugar de revisions= por modelo.
Fija los tres aunque solo sirvas dos. Un Router ofrece todos los checkpoints que conoce
independientemente de lo que precargues, así que una entrada sin fijar está a una decisión de enrutamiento
de cargarse sin fijar.
Por qué fijar no es lo predeterminado
Fijar por defecto rompería la carga desde una instantánea en caché más antigua, lo que importa para
despliegues en el dispositivo y aislados de la red: HF_HUB_OFFLINE=1 con una caché anterior a la
fijación dejaría de funcionar. Por eso Laya mantiene el valor por defecto del Hub a menos que pases una
revisión, y deja disponibles los SHAs revisados para cuando los quieras.
Verificar los digests de los artefactos
Una revisión fijada dice qué commit obtener. Un digest dice qué bytes esperas. Cada archivo que
listes se hashea antes de que se analice ninguno de ellos y antes de que los pesos lleguen al runtime. El
mapa es {path relative to the checkpoint: sha256 hex} — genéralo primero, luego pásalo.
¿Necesitas ambos?
Una revisión fijada ya fija el contenido: el Hub es git, así que el commit determina el árbol, y los archivos grandes se direccionan por su propio SHA-256. Si fijas y la descarga tiene éxito, tienes los bytes que nombra ese commit. Así que el digest no está ahí para repetir esa comprobación — difiere en qué confía.
Una revisión le pide un commit al Hub y cree la respuesta. Un digest es un registro que tú hiciste y tú conservas, comparado en cada carga. Eso aporta tres cosas que fijar no aporta:
- Cobertura para el caso común, que es sin fijar. Fijar es opcional y está desactivado por defecto, así que la mayoría de los despliegues siguen una rama móvil. Un digest es entonces lo único que nota un cambio.
- Una comprobación en tu propio disco. Tras la descarga, el checkpoint son archivos normales en una caché que cualquier cosa de la máquina puede editar. Nada los vuelve a verificar al cargar — salvo un digest.
- Independencia de la fuente. Si un espejo, un proxy o el propio Hub sirvieran bytes distintos, el digest es el único control que no le pide a la cosa bajo examen que responda por sí misma.
Esa independencia es también por lo que generar el mapa es un paso manual: una huella deja de ser un registro independiente en el momento en que la cosa que comprueba la produce por ti.
Generar el mapa
Genéralo a partir de un checkpoint que hayas revisado en lugar de copiar digests de cualquier sitio,
incluida esta página. Deliberadamente no hay ningún comando que lo produzca por ti: un mapa calculado a
partir de la copia que Laya acaba de descargar hashearía esos bytes y luego los verificaría contra sí
mismos. La comprobación vale algo solo porque una persona decidió que esos eran los bytes que quería, así
que generar el mapa es el paso donde queda registrada esa decisión. Un mapa pertenece a exactamente un
checkpoint: el repositorio bundle contiene un rl_agent_config.json distinto en su raíz (el checkpoint
inglés) que en multilingual/, así que un mapa generado desde uno fallará contra el otro.
import hashlib, json, os
CHECKPOINT = "/path/to/checkpoint" # the directory a load actually reads
FILES = [
"rl_agent_config.json",
"tokenizer/tokenizer.json",
"encoder/config.json",
"model.safetensors",
]
def sha256(path):
h = hashlib.sha256()
with open(path, "rb") as f:
for chunk in iter(lambda: f.read(1 << 20), b""):
h.update(chunk)
return h.hexdigest()
digests = {rel: sha256(os.path.join(CHECKPOINT, rel)) for rel in FILES}
with open("digests.json", "w") as f:
json.dump(digests, f, indent=2)
Una carga con Agent de torch analiza cinco archivos, y esos son cuatro de ellos. (ONNXAgent lee un
conjunto distinto y además acepta las claves onnx y onnx_path para hashear el propio grafo.) El quinto,
tokenizer/tokenizer_config.json, se deja fuera deliberadamente: Laya puede normalizarlo después de la
verificación y volverlo a escribir, en cuyo caso fijarlo hace fallar la carga siguiente. Esa
reescritura es condicional — se dispara solo cuando el archivo no declara ningún tokenizer_class, o
declara TokenizersBackend, o lleva extra_special_tokens como lista — así que en algunos checkpoints
nunca ocurre y fijar el archivo parecería funcionar. Dejarlo fuera es la opción portable, y significa que
un archivo analizado queda sin verificar. Consulta
Qué protege y qué no.
Usar el mapa
import json
import laya
with open("digests.json") as f:
agent = laya.load("convaiinnovations/laya", expected_sha256=json.load(f))
Las claves son rutas relativas al directorio del checkpoint. Un desajuste lanza ValueError, y un archivo
listado que no está lanza FileNotFoundError. Los archivos que no listes no se comprueban en absoluto,
así que el mapa es también la definición de lo que estás protegiendo. Esto funciona tanto en un directorio
local como en una descarga del Hub.
Sin tocar el código
LAYA_SHA256_DIGESTS contiene el mismo mapa como JSON y se aplica siempre que se llame a un cargador sin
un expected_sha256 explícito:
export LAYA_SHA256_DIGESTS="$(cat digests.json)"
laya-serve
Nada genera esto por ti: el valor es tu propio mapa, de un checkpoint que revisaste. Bajo Docker tiene que
estar en el entorno antes de que arranque compose, ya sea exportado como arriba o en el archivo .env
que lee compose — el servicio pasa ${LAYA_SHA256_DIGESTS:-}, así que una variable sin definir
significa silenciosamente que no hay verificación:
echo "LAYA_SHA256_DIGESTS=$(cat digests.json)" >> .env
docker compose -f compose.yaml -f compose.http.yaml up laya-serve
No hay una variante _FILE de esta variable: esa indirección existe para los secretos, y un mapa de
digests no lo es.
Nombra cada checkpoint cuando un proceso carga más de uno. La variable toma dos formas, y los tipos de los valores dicen cuál:
# one set of files, checked on every checkpoint the process loads
LAYA_SHA256_DIGESTS='{"rl_agent_config.json": "<sha256>"}'
# a map per checkpoint, which is what a router serving several needs
LAYA_SHA256_DIGESTS='{"english": {"rl_agent_config.json": "<sha256>"},
"multilingual": {"rl_agent_config.json": "<sha256>"}}'
La forma plana es la lectura propia de verify_digests y aplica las mismas rutas a todo, así que en un
router solo puede coincidir con un checkpoint y rechaza el resto. El repositorio empaquetado distribuye un
model.safetensors y un rl_agent_config.json distintos por checkpoint, así que nómbralos:
flat map generated from the english checkpoint
load english ok
load multilingual ValueError: laya: SHA-256 mismatch for rl_agent_config.json
Un checkpoint que el mapa anidado no nombra queda deliberadamente sin fijar en lugar de ser un error, y un
nombre de modelo que el router no conoce lanza una excepción en lugar de dejar ese checkpoint sin
verificar. en se resuelve a english, la misma normalización que aplica Router(sha256_digests=...).
Las dos rutas difieren en ese último punto, algo fácil de tropezar si usas ambas. Un checkpoint que el
mapa anidado del entorno omite queda fijado a un mapa vacío, así que un mapa plano no puede colarse en
él. Un checkpoint omitido de Router(sha256_digests=...) en el código no tiene ninguna entrada, así que
sigue recayendo en lo que diga el entorno. Nombra cada checkpoint que quieras fijar en el que uses.
Una variable sin definir o vacía significa que no hay verificación, así que es seguro dejarla fuera de entornos que no la necesitan. Un JSON mal formado lanza una excepción en lugar de saltarse la comprobación silenciosamente, y mezclar las dos formas en un mismo objeto se rechaza por su nombre.
Cómo se ve un desajuste en el servidor
Cómo aflora depende de la precarga. Un laya-serve sin más precarga por defecto (LAYA_PRELOAD=1), así
que un desajuste falla al arrancar — de forma ruidosa y determinista. Los contenedores de este
repositorio ponen LAYA_PRELOAD=0 (compose.http.yaml, y Docker documenta la anulación), así
que ahí la primera carga ocurre en una solicitud y no se verifica nada hasta entonces. Un desajuste es
entonces un 422 en el ticket que sea que enrute a ese checkpoint: laya/serve.py mapea el ValueError
a HTTPException(422) y devuelve el texto del digest al llamador. Un archivo listado pero ausente lanza
FileNotFoundError en su lugar, que cae en un 500 “inference failed” genérico con la razón solo en el
registro del contenedor.
Cuenta con el 422. Se clasifica como error de cliente en registros, paneles y reglas de alerta, así que el sitio por defecto donde un operador busca un despliegue roto es el único donde esto no aparecerá.
Fijar una revisión en el servidor
LAYA_REVISION contiene un commit, rama o etiqueta aplicado a cada descarga de checkpoint, o la palabra
reviewed, que busca cada repositorio en PINNED_REVISIONS y usa su propio SHA:
LAYA_REVISION=reviewed laya-serve
reviewed para un repositorio para el que la tabla no tiene entrada lanza una excepción en lugar de
cargarlo sin fijar — una fijación que se resuelve silenciosamente en nada es el fallo que este control
existe para evitar. Un argumento revision= explícito sigue ganando a la variable, y sin definir o en
blanco significa “no se pidió”, así que una caché HF_HUB_OFFLINE=1 sigue cargando exactamente como antes.
En código, Router acepta ambos por modelo:
router = laya.Router(
revisions={"english": PINNED_REVISIONS["convaiinnovations/laya"]},
sha256_digests={"english": {"rl_agent_config.json": "<sha256>"}},
)
Los digests son siempre por modelo — no hay equivalente de revision para todo el router, porque un SHA
de commit se puede compartir entre checkpoints y un digest no. Consulta Docker para las
variables de despliegue y Router para el constructor completo.
Cuando se actualiza un checkpoint
Los dos controles se comportan de forma distinta, y solo uno de ellos necesita algo de tu parte.
Una revisión fijada te mantiene donde estás. Un checkpoint nuevo no llega a un despliegue fijado hasta
que cambias la fijación, que es el sentido de fijar. PINNED_REVISIONS se mueve con la biblioteca, así
que tomar un commit revisado más nuevo significa actualizar Laya, no editar un SHA.
Los digests detienen la carga, a propósito. Tu mapa se generó a partir de bytes que revisaste. Bytes
distintos lanzan ValueError antes de que se analice nada:
ValueError: laya: SHA-256 mismatch for rl_agent_config.json: expected ae287b56…, got 25061739…
Eso es la función trabajando, no un bug que haya que esquivar. El orden importa:
- Averigua por qué cambiaron los bytes — una publicación prevista, o algo que no esperabas.
- Revisa el checkpoint nuevo.
- Regenera el mapa a partir de la copia revisada.
- Despliega el mapa nuevo.
No te saltes al paso 3. Volver a ejecutar el generador contra lo que acaba de llegar hace que la comprobación pase y no verifica nada — registra los bytes nuevos como de confianza porque están presentes, que es precisamente el estado que el digest existía para detectar.
Dos detalles. Un mapa nuevo entregado a través de LAYA_SHA256_DIGESTS necesita que se reinicie el
proceso, porque un servidor en ejecución conserva el entorno con el que arrancó. Y esta secuencia solo se
aplica a un despliegue que no está fijado por revisión: con ambos controles activos, los bytes nuevos
nunca llegan hasta que mueves la fijación.
Confirmar qué se cargó realmente
Cada agente registra el commit del que vino, None para un directorio local:
agent.revision # Agent and ONNXAgent
router.loaded_revisions # {"english": "55cf4c4e…", …} for each resident agent
agent.revision informa la instantánea a la que se resolvió la descarga, recayendo en lo que hayas
pasado, así que fijar por rama o etiqueta repite ese nombre en lugar de un SHA — fija por SHA si quieres
que este campo sea uno. Cargar desde un directorio local informa None, y revision se ignora ahí,
porque no hay instantánea del Hub que resolver.
El servidor informa lo mismo, que es la forma más rápida de confirmar que un despliegue está ejecutando el checkpoint que crees:
curl -s localhost:8000/health
# {"status":"ok","loaded":["english"],"revisions":{"english":"55cf4c4e…"},"device":"auto"}
laya-ts
El paquete de TypeScript replica las partes de fijación y digests — revision, expectedSha256, y leer
la revisión de vuelta. No tiene equivalente de LAYA_SHA256_DIGESTS ni servidor, así que las dos
secciones anteriores no se le aplican:
import { loadNodeBundle, PINNED_REVISIONS } from "laya-ts";
const bundle = await loadNodeBundle("convaiinnovations/laya", {
revision: PINNED_REVISIONS["convaiinnovations/laya"],
expectedSha256: { "rl_agent_config.json": "<sha256 of that file>" },
});
Una revisión explícita se une a la ruta de caché en disco bajo ~/.cache/laya-ts/, así que los artefactos
fijados de forma distinta nunca chocan. En el navegador la revisión viaja en la URL de la solicitud, que
indexa CacheStorage de la misma manera. createNodeProvider acepta expectedSha256 para los grafos
ONNX que carga.
Qué protege y qué no
Detecta un checkpoint cuyo contenido cambió respecto a lo que revisaste — una edición en un repositorio upstream, un espejo comprometido, una descarga corrupta o una copia local modificada.
No hace seguro un checkpoint sin revisar. Un digest solo dice que los bytes coinciden con lo que registraste; decidir que esos bytes son de confianza sigue siendo cosa tuya.
Tres límites que conviene conocer antes de confiar en esto:
- Solo se comprueban los archivos listados. No hay ningún modo “verificar todo” ni forma de rechazar un archivo que no listaste, así que un artefacto que falte en tu mapa se carga sin verificar. El mapa es la frontera de la garantía.
- Un archivo analizado queda por tanto fuera de ella.
tokenizer/tokenizer_config.jsonse analiza, pero Laya puede normalizarlo y volverlo a escribir inmediatamente después de la comprobación del digest, así que fijarlo puede tener éxito en la primera carga y fallar en la siguiente. El mapa recomendado lo deja fuera por esa razón, lo que significa que sus bytes no se verifican. La reescritura depende de lo que declare el archivo, así que si ocurre o no depende del checkpoint. - La verificación ocurre solo en tiempo de carga. Nada vuelve a comprobar un archivo después, ya sea reemplazado por un atacante o por el propio proceso.