Status (planned — not yet implemented). Today (0.1.9) unset
precisionresolves to a static fp16 on CUDA / fp32 on CPU fallback (codai/backends/cuda.py:770-774,codai/models/hf_loading.pyresolve_dtype). This document specifies making the default model-native (read from the checkpoint) while keepingprecisionas an explicit force. Companion notes:docs/frontend-engine-split.md, memoryproject_model_config_respect.
The HF nvidia engine (and the shared hf_loading.py path) pick a compute dtype
from a blanket fallback when the model's precision is unset. fp16 is wrong for
most modern checkpoints:
- Qwen3.x, Llama-3.x, Gemma, Mistral, etc. are trained and shipped in bf16. Loading them in fp16 risks activation/logit overflow (Qwen is notably prone to this) and silently diverges from the reference numerics.
- It also produced the observed warning: with
precision: nullthe model loaded as float32, and Flash-Attention-2 (which only supports fp16/bf16) emitted "Flash Attention 2 only supports torch.float16 and torch.bfloat16 dtypes, but the current dtype in Qwen3_5ForCausalLM is torch.float32".
The authoritative signal already exists and we ignore it: every HF checkpoint's
config.json carries torch_dtype (newer transformers: dtype) — exactly
the dtype the model was trained/saved in.
precisioninmodels.json— explicit force. Accepted values:fp16/float16,bf16/bfloat16,fp32/float32. The literal valueauto(or unset / empty) means "fall through to the next step", not fp16.- Checkpoint
config.jsontorch_dtype(fallback todtypekey for newer transformers schemas) — the model-native dtype. This is the "automatic based on the model" behaviour. Qwen/Llama/Gemma → bf16; genuinely fp16-native checkpoints → fp16. - Final fallback when the checkpoint has no usable dtype (missing,
"auto", orfloat32while loading on GPU):- bf16 on CUDA — every Ampere+ GPU in this fleet supports it, and it is strictly safer than fp16 (wider exponent, no overflow).
- fp32 on CPU — no bf16/fp16 benefit without a GPU; keep full precision.
Flash-Attention-2 accepts only fp16/bf16. If the resolved dtype is fp32 and FA2 is requested (per-model or global flash flag), promote to bf16 rather than silently downgrading attention to sdpa. Rationale: the user asked for FA2; honour it with the nearest valid dtype instead of quietly changing the attention backend (which changes numerics/perf without telling anyone). Only fall back to sdpa when FA2 is genuinely unavailable (kernel not built / not installed).
codai/models/hf_loading.py—resolve_dtype: extend the signature to accept the checkpoint source so step 2 can read it, e.g.resolve_dtype(cfg, default=None, model_path=None). Whencfg['precision']is unset/auto, read<model_path>/config.json→torch_dtype/dtype. If still unresolved, apply the CUDA→bf16 / CPU→fp32 fallback. Map dtype strings totorch.*via the existing alias table. Keep it a pure function (no model load) — it only reads the smallconfig.json.- Cache the parsed
config.jsondtype per model dir to avoid re-reading on every load (optional; the file is tiny). - For GGUF/llama.cpp this is a no-op — quant type is baked into the file;
precisiondoes not apply there.
- Cache the parsed
codai/backends/cuda.py: the load path already has the model dir; pass it asmodel_pathtoresolve_dtype(currently calls it with only{'precision': kwargs.get('precision')}at ~line 769). Apply the FA2 guard whereattn_implementationis chosen (~line 977-984): if_cfg_dtype is torch.float32andself.use_flash_attn and self.flash_attn_available, set_cfg_dtype = torch.bfloat16beforeload_kwargs['dtype']is set, and log the promotion.- Both call sites in
manager.pyalready forwardprecisionviakwargs['precision'] = config.get('precision'); no change needed there beyond ensuring the model path is available to the backend (it already is). - Parity: apply the same
resolve_dtype(..., model_path=...)upgrade to the spatial/embedding/audio/vision builders inhf_loading.pyso every HF path benefits, not just text.
- Force a specific dtype:
{ "name": "Qwen/Qwen3.5-9B", "precision": "bf16" } - Let the engine decide from the checkpoint: omit
precisionor set"precision": "auto". With this design,Qwen/Qwen3.5-9B(configtorch_dtype: bfloat16) auto-loads as bf16 with no edit.
Qwen/Qwen3.5-9Bwithprecisionunset → loads bf16, no FA2 dtype warning.- A genuinely fp16-native checkpoint (
config.json torch_dtype: float16) withprecisionunset → loads fp16. precision: "fp32"+ FA2 enabled → dtype promoted to bf16, promotion logged; FA2 stays active.- CPU-only load with
precisionunset and no GPU → fp32. - GGUF model →
precisionignored, no regression.
- Per-model
flash_attention: falseis currently overridden by the globaloffload.flash_attentionflag (self.use_flash_attnderives from the global). If per-model opt-out should win, that precedence fix is separate from dtype selection and tracked on its own. - Switching the global unset-default to bf16 is subsumed by step 3 above and needs no separate flag once this lands.