123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157(*---------------------------------------------------------------------------
Copyright (c) 2026 Anil Madhavapeddy. All rights reserved.
SPDX-License-Identifier: ISC
---------------------------------------------------------------------------*)moduleModel=structtypet={name:string;aliases:stringlist;files:stringlist;descr:string;}lethf_repo="antirez/deepseek-v4-gguf"letall=[{name="q2-imatrix";aliases=["q2"];files=["DeepSeek-V4-Flash-IQ2XXS-w2Q2K-AProjQ8-SExpQ8-OutQ8-chat-v2-imatrix.gguf";];descr="2-bit Flash routed experts (~81 GB); for 96-128 GB RAM.";};{name="q2-q4-imatrix";aliases=["q2q4"];files=["DeepSeek-V4-Flash-Layers37-42Q4KExperts-OtherExpertLayersIQ2XXSGateUp-Q2KDown-AProjQ8-SExpQ8-OutQ8-chat-v2-imatrix-fixed.gguf";];descr="Mixed Flash quant (~98 GB); higher quality for 128 GB.";};{name="q4-imatrix";aliases=["q4"];files=["DeepSeek-V4-Flash-Q4KExperts-F16HC-F16Compressor-F16Indexer-Q8Attn-Q8Shared-Q8Out-chat-v2-imatrix.gguf";];descr="4-bit Flash routed experts (~153 GB); for 256 GB+ RAM.";};{name="pro-q2-imatrix";aliases=["pro-q2"];files=["DeepSeek-V4-Pro-IQ2XXS-w2Q2K-AProjQ8-SExpQ8-OutQ8-Instruct-imatrix.gguf";];descr="PRO q2 single file (~430 GB); for 512 GB RAM.";};{name="pro-q4-layers00-30";aliases=["pro-q4-a"];files=["DeepSeek-V4-Pro-Q4K-Layers00-30.gguf"];descr="PRO Q4 layers 0..30 (~426 GB); distributed coordinator.";};{name="pro-q4-layers31-output";aliases=["pro-q4-b"];files=["DeepSeek-V4-Pro-Q4K-Layers-31-output.gguf"];descr="PRO Q4 layers 31..output (~412 GB); distributed worker.";};{name="pro-q4-split";aliases=["pro-q4"];files=["DeepSeek-V4-Pro-Q4K-Layers00-30.gguf";"DeepSeek-V4-Pro-Q4K-Layers-31-output.gguf";];descr="Both PRO Q4 split files (~838 GB total).";};]letfinds=List.find_opt(funm->m.name=s||List.memsm.aliases)all(* xdge scopes paths to the app name ("ds4"), so its data dir is already
XDG_DATA_HOME/ds4; render it to a plain path for the string-based helpers
below. *)letdirxdg=Eio.Path.native_exn(Xdge.data_dirxdg)letpresent~dirm=List.for_all(funf->Sys.file_exists(Filename.concatdirf))m.files(* First *.gguf in [d] in lexical order, if any. *)letfirst_ggufd=matchSys.readdirdwith|exceptionSys_error_->None|entries->(Array.to_listentries|>List.filter(funf->Filename.check_suffixf".gguf")|>List.sortString.compare|>function|[]->None|f::_->Some(Filename.concatdf))letresolve?(env=Sys.getenv_opt)~dir:dataoverride=matchoverridewith|Somes->(matchfindswith|Some{files=[f];_}->letpath=Filename.concatdatafinifSys.file_existspaththenOkpathelseError(Printf.sprintf"model '%s' is not downloaded; run 'ds4 download %s'"ss)|Some{name;files;_}->Error(Printf.sprintf"model '%s' is split across %d files (%s) and cannot be \
loaded as a single model"name(List.lengthfiles)(String.concat", "files))|None->Oks(* unknown name: treat as a filesystem path *))|None->(matchenv"DS4_MODEL"with|Somem->Okm|None->(matchfirst_ggufdatawith|Somem->Okm|None->Error(Printf.sprintf"no model found: pass --model PATH|ALIAS, set DS4_MODEL, \
run 'ds4 download <target>', or place a .gguf in %s"data)))openCmdlinerletarg=Arg.(value&opt(somestring)None&info["m";"model"]~docv:"MODEL"~doc:"GGUF model: a download target name or alias (e.g. 'q4'), or a \
path to a .gguf file. When omitted, the DS4_MODEL environment \
variable is used, then the first *.gguf in the data directory \
(XDG_DATA_HOME/ds4).")lettarget_arg=letenum_assoc=List.concat_map(funm->(m.name,m)::List.map(funa->(a,m))m.aliases)allinletdoc="Model to download, by name or alias (run 'ds4 list' for the full table)."inArg.(required&pos0(some(enumenum_assoc))None&info[]~docv:"TARGET"~doc)end