Compare commits
17
Commits
main
...
laguna-s2.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
448d5695d1 | ||
|
|
d8d9688584 | ||
|
|
8f620f31e2 | ||
|
|
5f3c78da2e | ||
|
|
0ee6d45650 | ||
|
|
31db3a87c5 | ||
|
|
30f4e1785c | ||
|
|
6aec2354be | ||
|
|
62b59a48e2 | ||
|
|
bf605b17d8 | ||
|
|
5c5f95a58d | ||
|
|
6bd7fd37df | ||
|
|
3b0ec5f1d8 | ||
|
|
938227a248 | ||
|
|
92347005d2 | ||
|
|
7e3dbef7e3 | ||
|
|
8a927009e6 |
No files matched your search
@@ -243,7 +243,8 @@ ds4_agent_cpu.o: ds4_agent.c ds4.h ds4_ssd.h ds4_distributed.h ds4_help.h ds4_kv
|
||||
ds4_metal.o: ds4_metal.m ds4_gpu.h $(METAL_SRCS)
|
||||
$(CC) $(OBJCFLAGS) -c -o $@ ds4_metal.m
|
||||
|
||||
ds4_cuda.o: ds4_cuda.cu ds4_gpu.h ds4_gpu_mgpu.h ds4_iq2_tables_cuda.inc
|
||||
ds4_cuda.o: ds4_cuda.cu ds4_gpu.h ds4_gpu_mgpu.h ds4_iq2_tables_cuda.inc \
|
||||
rocm/ds4_rocm_laguna.cuh rocm/ds4_rocm_dflash.cuh
|
||||
$(NVCC) $(NVCCFLAGS) -c -o $@ ds4_cuda.cu
|
||||
|
||||
ds4_rocm.o: ds4_rocm.cu ds4_gpu.h ds4_iq2_tables_cuda.inc $(ROCM_SRCS)
|
||||
|
||||
@@ -3,10 +3,11 @@
|
||||
</p>
|
||||
|
||||
**DwarfStar** is a small native inference engine optimized first for
|
||||
**DeepSeek V4 Flash**. It also supports **GLM 5.2** and, on very high-memory
|
||||
machines, **DeepSeek V4 PRO**. It is self-contained and deliberately narrow,
|
||||
not a general GGUF runner. Model loading, prompt rendering, tool calls, KV
|
||||
state, the HTTP server, and the coding agent are built and tested together.
|
||||
**DeepSeek V4 Flash**. It also supports **GLM 5.2**, **Laguna S 2.1**, and, on
|
||||
very high-memory machines, **DeepSeek V4 PRO**. It is self-contained and
|
||||
deliberately narrow, not a general GGUF runner. Model loading, prompt rendering,
|
||||
tool calls, KV state, the HTTP server, and the coding agent are built and tested
|
||||
together.
|
||||
The repository also includes tools and data for GGUF, imatrix, quality, and speed.
|
||||
|
||||
Supported backends:
|
||||
@@ -88,11 +89,12 @@ next sections.
|
||||
|
||||
## Model Weights
|
||||
|
||||
This implementation only works with the DeepSeek V4 and GLM 5.2 GGUFs listed
|
||||
below. It is not a general GGUF loader, and arbitrary GGUF files will not have
|
||||
the tensor layout, quantization mix, metadata, or optional MTP state expected by
|
||||
the engine. The 2 bit quantizations provided here are verified to be actually
|
||||
high quality: they behave well, work under coding agents, call tools in a reliable way.
|
||||
This implementation only works with the DeepSeek V4, GLM 5.2, and Laguna S 2.1
|
||||
GGUFs listed below. It is not a general GGUF loader, and arbitrary GGUF files
|
||||
will not have the tensor layout, quantization mix, metadata, or optional MTP
|
||||
state expected by the engine. The 2 bit DeepSeek and GLM quantizations provided
|
||||
here are verified to be actually high quality: they behave well, work under
|
||||
coding agents, and call tools reliably.
|
||||
|
||||
The 2 bit quants use a very asymmetrical quantization: only the routed MoE
|
||||
experts are quantized, up/gate at `IQ2_XXS`, down at `Q2_K`. They are the
|
||||
@@ -106,6 +108,8 @@ Download one main model. **Prefer the imatrix versions.**
|
||||
./download_model.sh q2-q4-imatrix # 96/128 GB RAM machines, q2 with last 6 layers q4
|
||||
./download_model.sh q4-imatrix # >= 256 GB RAM machines, imatrix-tuned q4
|
||||
./download_model.sh pro-q2-imatrix # 512 GB RAM machines, PRO q2 imatrix quant
|
||||
./download_model.sh laguna-q4 # >= 96 GB unified memory, official Poolside Q4_K_M
|
||||
./download_model.sh laguna-q2-q3 # 64 GB class, mixed routed Q2_K/Q3_K
|
||||
```
|
||||
|
||||
For the full PRO Q4 distributed run, download one half on each machine:
|
||||
@@ -170,6 +174,114 @@ GLM inference uses the Metal, CUDA, or ROCm graph backend. Directional steering,
|
||||
`--power` below 100, an explicit `--prefill-chunk`, and the external `--mtp`
|
||||
file are not supported for GLM yet.
|
||||
|
||||
Laguna S 2.1 support targets Poolside's official imatrix-quantized Q4_K_M GGUF.
|
||||
The current 63.56 GiB recipe uses Q4_K routed experts and Q8_0 signal-path
|
||||
weights. It runs with full residency on Metal, CUDA, or ROCm. DwarfStar also accepts
|
||||
Poolside's earlier 70.01 GiB recipe with F16 attention and mixed Q4_K/Q6_K
|
||||
experts on Metal. These files fit comfortably on a 96 or 128 GiB machine.
|
||||
|
||||
For lower-memory systems, `laguna-q2-q3` downloads a 44.95 GiB mixed quant. It
|
||||
retains the official file's dense weights, uses Q2_K routed experts in layers
|
||||
1 through 20, and Q3_K routed experts in layers 21 through 47. This is the
|
||||
smaller Laguna layout and is intended for full residency on 64 GB class
|
||||
systems. The current Q8_0-signal Q4_K_M and mixed Q2_K/Q3_K layouts run on
|
||||
Metal, CUDA, and ROCm. Linux validation covers NVIDIA GB10 in DGX Spark and the
|
||||
Ryzen AI Max+ 395 / Radeon 8060S (`gfx1151`) in Strix Halo.
|
||||
|
||||
CLI, agent, and server use Laguna's native chat, interleaved reasoning, and
|
||||
tagged tool-call formats:
|
||||
|
||||
```sh
|
||||
./download_model.sh laguna-q4
|
||||
./ds4 -m gguf/laguna-s-2.1-Q4_K_M.gguf -c 32768 -p "Explain this repository"
|
||||
./ds4-agent -m gguf/laguna-s-2.1-Q4_K_M.gguf -c 32768
|
||||
./ds4-server -m gguf/laguna-s-2.1-Q4_K_M.gguf -c 32768
|
||||
```
|
||||
|
||||
Poolside's standalone DFlash model can accelerate greedy Laguna decoding on
|
||||
Metal, CUDA, and ROCm without changing the generated tokens. The download target uses
|
||||
a 1.04 GiB Q8_0 quant of Poolside's support model: it drafts faster and uses
|
||||
half the memory of BF16, while the full Laguna model still verifies every
|
||||
accepted token. Download it separately and pass it alongside any supported
|
||||
Laguna GGUF:
|
||||
|
||||
```sh
|
||||
./download_model.sh laguna-dflash
|
||||
./ds4 -m gguf/laguna-s-2.1-Q4_K_M.gguf \
|
||||
--dflash gguf/laguna-s-2.1-DFlash-Q8_0.gguf --temp 0
|
||||
./ds4-agent -m gguf/laguna-s-2.1-Q4_K_M.gguf \
|
||||
--dflash gguf/laguna-s-2.1-DFlash-Q8_0.gguf
|
||||
./ds4-server -m gguf/laguna-s-2.1-Q4_K_M.gguf \
|
||||
--dflash gguf/laguna-s-2.1-DFlash-Q8_0.gguf
|
||||
```
|
||||
|
||||
The default explores up to 15 draft positions on CUDA and up to three on Metal
|
||||
and ROCm. It stops before
|
||||
verification when a proposal's probability is below 0.4. Use
|
||||
`--dflash-draft N` to tune the 1 through 15 range and
|
||||
`--dflash-p-min P` to tune the confidence cutoff; set the latter to `0` to
|
||||
keep a fixed verifier width. A nonzero cutoff is faster, but varying the
|
||||
verifier batch width can resolve nearly tied greedy logits differently because
|
||||
floating-point reductions are not batch-invariant. Use `0` when fixed-width
|
||||
reproducibility matters. DwarfStar automatically falls back to ordinary
|
||||
Laguna decoding when sampling is stochastic or when speculation is slower for
|
||||
the current turn. Set `DS4_DFLASH_TIMING=1` to print per-cycle draft and
|
||||
verify timings.
|
||||
|
||||
For Strix Halo:
|
||||
|
||||
```sh
|
||||
./download_model.sh laguna-q2-q3
|
||||
make strix-halo
|
||||
./ds4 --rocm -m gguf/laguna-s-2.1-RoutedQ2_K-Last27Q3_K.gguf -c 8192
|
||||
./ds4-agent --rocm -m gguf/laguna-s-2.1-RoutedQ2_K-Last27Q3_K.gguf -c 8192
|
||||
./ds4-server --rocm -m gguf/laguna-s-2.1-RoutedQ2_K-Last27Q3_K.gguf -c 8192
|
||||
```
|
||||
|
||||
For DGX Spark:
|
||||
|
||||
```sh
|
||||
./download_model.sh laguna-q2-q3
|
||||
make cuda-spark
|
||||
./ds4 --cuda -m gguf/laguna-s-2.1-RoutedQ2_K-Last27Q3_K.gguf -c 8192
|
||||
./ds4-agent --cuda -m gguf/laguna-s-2.1-RoutedQ2_K-Last27Q3_K.gguf -c 8192
|
||||
./ds4-server --cuda -m gguf/laguna-s-2.1-RoutedQ2_K-Last27Q3_K.gguf -c 8192
|
||||
```
|
||||
|
||||
On the development DGX Spark with that mixed Q2_K/Q3_K GGUF, a 4,096-token
|
||||
prefill runs at about 580 tokens/s and generation after that prompt at about
|
||||
26.7 tokens/s. Long CUDA prefills compact routed tokens by expert, dequantize
|
||||
only active expert matrices, and use variable-size FP16 tensor-core GEMMs with
|
||||
FP32 projection outputs. Decode reuses each Laguna KV-cache row across grouped
|
||||
query heads and uses denser Q2_K/Q3_K warp layouts; short prefills retain the
|
||||
quantized matvec kernels. Set `DS4_CUDA_MOE_NO_TC_PREFILL=1` to select the
|
||||
quantized long-prefill fallback. The decode optimizations can be isolated with
|
||||
`DS4_CUDA_LAGUNA_NO_GQA_DECODE=1`, `DS4_CUDA_MOE_NO_Q2_HALFWARP=1`, and
|
||||
`DS4_CUDA_MOE_NO_Q3_GROUP_DOWN=1`.
|
||||
|
||||
On the development Strix Halo with the official Q4_K_M GGUF, a 2,048-token
|
||||
prefill runs at about 252 tokens/s and short-context generation at about 25.8
|
||||
tokens/s; after an 8,192-token prompt prefill is about 194 tokens/s and
|
||||
generation about 24.1 tokens/s. The smaller mixed Q2_K/Q3_K quant is faster
|
||||
still. Laguna currently requires full model residency; SSD streaming,
|
||||
distributed inference, and tensor parallelism are not implemented.
|
||||
|
||||
DFlash speculative decoding works on ROCm. With a fixed verifier width
|
||||
(`--dflash-p-min 0`) it is token-exact against ordinary decoding in the
|
||||
regression corpus, but on this GPU it is only a win on highly predictable
|
||||
text. Each draft position routes to its own ten experts, so the verifier's
|
||||
expert traffic grows with the draft length while a Strix Halo decode step is
|
||||
already bandwidth-bound; break-even needs roughly 60% of draft tokens accepted.
|
||||
DwarfStar measures throughput for the first cycles of every turn and drops back
|
||||
to ordinary decoding when speculation is not paying, so leaving `--dflash`
|
||||
enabled is safe.
|
||||
|
||||
The shipped GGUF is configured for a 262144-token context. Laguna defaults to
|
||||
temperature 0.7, top-k 20, top-p 0.95, and min-p 0.05; explicit sampling
|
||||
options always take precedence. Use `--nothink` or the
|
||||
`laguna-s-2.1-chat` server alias for direct replies, and preserve reasoning
|
||||
content between tool calls when building a client.
|
||||
|
||||
Then build:
|
||||
|
||||
```sh
|
||||
|
||||
+56
-2
@@ -3,6 +3,9 @@ set -e
|
||||
|
||||
GLM_UNSLOTH_REPO="unsloth/GLM-5.2-GGUF"
|
||||
GLM_ANTIREZ_REPO="antirez/GLM-5.2-GGUF"
|
||||
LAGUNA_REPO="poolside/Laguna-S-2.1-GGUF"
|
||||
LAGUNA_ANTIREZ_REPO="antirez/Laguna-S-2.1-GGUF"
|
||||
LAGUNA_REVISION="706fa69799926b6afde1af9e24ca2a4923f110a1"
|
||||
REPO="antirez/deepseek-v4-gguf"
|
||||
Q2_IMATRIX_FILE="DeepSeek-V4-Flash-IQ2XXS-w2Q2K-AProjQ8-SExpQ8-OutQ8-chat-v2-imatrix.gguf"
|
||||
Q4_IMATRIX_FILE="DeepSeek-V4-Flash-Q4KExperts-F16HC-F16Compressor-F16Indexer-Q8Attn-Q8Shared-Q8Out-chat-v2-imatrix.gguf"
|
||||
@@ -18,6 +21,9 @@ GLM_UNSLOTH_Q4_FIRST_FILE="$GLM_UNSLOTH_Q4_LOCAL_BASE-00001-of-00011.gguf"
|
||||
GLM_ANTIREZ_IQ2XXS_FILE="GLM-5.2-UD-IQ2_XXS_RoutedIQ2XXS_blk78Q2K.gguf"
|
||||
GLM_ANTIREZ_Q2_FILE="GLM-5.2-UD-Q2_K_RoutedQ2K.gguf"
|
||||
GLM_ANTIREZ_Q4_FILE="GLM-5.2-UD-Q4_K_RoutedQ4K.gguf"
|
||||
LAGUNA_Q4_FILE="laguna-s-2.1-Q4_K_M.gguf"
|
||||
LAGUNA_Q2_Q3_FILE="laguna-s-2.1-RoutedQ2_K-Last27Q3_K.gguf"
|
||||
LAGUNA_DFLASH_FILE="laguna-s-2.1-DFlash-Q8_0.gguf"
|
||||
|
||||
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||
OUT_DIR=${DS4_GGUF_DIR:-"$ROOT/gguf"}
|
||||
@@ -26,6 +32,7 @@ case "$OUT_DIR" in
|
||||
*) OUT_DIR="$ROOT/$OUT_DIR" ;;
|
||||
esac
|
||||
TOKEN=${HF_TOKEN:-}
|
||||
HF_REVISION=
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
@@ -45,6 +52,9 @@ Usage:
|
||||
./download_model.sh glm-antirez-iq2xxs [--token TOKEN]
|
||||
./download_model.sh glm-antirez-q2 [--token TOKEN]
|
||||
./download_model.sh glm-antirez-q4 [--token TOKEN]
|
||||
./download_model.sh laguna-q4 [--token TOKEN]
|
||||
./download_model.sh laguna-q2-q3 [--token TOKEN]
|
||||
./download_model.sh laguna-dflash [--token TOKEN]
|
||||
|
||||
Targets:
|
||||
|
||||
@@ -103,6 +113,22 @@ Targets:
|
||||
GLM 5.2 antirez routed Q4_K GGUF from antirez/GLM-5.2-GGUF.
|
||||
About 434 GB on disk.
|
||||
|
||||
laguna-q4
|
||||
Official imatrix-quantized Laguna S 2.1 Q4_K_M GGUF from Poolside.
|
||||
About 68 GB on disk; supported by Metal, CUDA, and ROCm with full model
|
||||
residency.
|
||||
|
||||
laguna-q2-q3
|
||||
Mixed Laguna S 2.1 routed-expert quant for 64 GB systems. Routed
|
||||
layers 1..20 use Q2_K and layers 21..47 use Q3_K; all other tensors
|
||||
retain the official Q4_K_M layout. 44.95 GiB on disk; supported by
|
||||
Metal, CUDA, and ROCm with full model residency.
|
||||
|
||||
laguna-dflash
|
||||
Q8_0 Laguna S 2.1 DFlash speculative support GGUF, quantized from
|
||||
Poolside's official support model. About 1.04 GiB on disk. This is an
|
||||
optional support model and does not replace or relink the main model.
|
||||
|
||||
Options:
|
||||
--token TOKEN Hugging Face token. Otherwise HF_TOKEN or the local HF token
|
||||
cache is used if present.
|
||||
@@ -124,7 +150,7 @@ After downloading mtp, enable it explicitly, for example:
|
||||
After downloading DSpark support, enable it explicitly in greedy mode:
|
||||
./ds4 --dspark --mtp <download directory>/$DSPARK_SUPPORT_FILE --temp 0
|
||||
|
||||
PRO and GLM files are downloaded with the official Hugging Face downloader
|
||||
Large PRO, GLM, and Laguna files use the official Hugging Face downloader
|
||||
because they are too large, sharded, or nested for the curl path used by the
|
||||
smaller DeepSeek Flash GGUF files.
|
||||
EOF
|
||||
@@ -180,6 +206,23 @@ case "$MODEL" in
|
||||
MODEL_FILE=$GLM_ANTIREZ_Q4_FILE
|
||||
FORCE_HF_DOWNLOAD=1
|
||||
;;
|
||||
laguna-q4)
|
||||
REPO=$LAGUNA_REPO
|
||||
MODEL_FILE=$LAGUNA_Q4_FILE
|
||||
FORCE_HF_DOWNLOAD=1
|
||||
HF_REVISION=$LAGUNA_REVISION
|
||||
;;
|
||||
laguna-q2-q3)
|
||||
REPO=$LAGUNA_ANTIREZ_REPO
|
||||
MODEL_FILE=$LAGUNA_Q2_Q3_FILE
|
||||
FORCE_HF_DOWNLOAD=1
|
||||
;;
|
||||
laguna-dflash)
|
||||
REPO=$LAGUNA_ANTIREZ_REPO
|
||||
MODEL_FILE=$LAGUNA_DFLASH_FILE
|
||||
FORCE_HF_DOWNLOAD=1
|
||||
LINK_MODEL=0
|
||||
;;
|
||||
-h|--help|help)
|
||||
usage
|
||||
exit 0
|
||||
@@ -281,11 +324,18 @@ download_one_hf() {
|
||||
|
||||
echo "Downloading $file"
|
||||
echo "from https://huggingface.co/$REPO"
|
||||
if [ -n "$HF_REVISION" ]; then
|
||||
echo "revision $HF_REVISION"
|
||||
fi
|
||||
echo "using $HF_CMD download"
|
||||
echo "If the download stops, run the same command again to resume it."
|
||||
|
||||
if [ -n "$TOKEN" ]; then
|
||||
if [ -n "$TOKEN" ] && [ -n "$HF_REVISION" ]; then
|
||||
"$HF_CMD" download "$REPO" "$file" --revision "$HF_REVISION" --repo-type model --local-dir "$OUT_DIR" --token "$TOKEN"
|
||||
elif [ -n "$TOKEN" ]; then
|
||||
"$HF_CMD" download "$REPO" "$file" --repo-type model --local-dir "$OUT_DIR" --token "$TOKEN"
|
||||
elif [ -n "$HF_REVISION" ]; then
|
||||
"$HF_CMD" download "$REPO" "$file" --revision "$HF_REVISION" --repo-type model --local-dir "$OUT_DIR"
|
||||
else
|
||||
"$HF_CMD" download "$REPO" "$file" --repo-type model --local-dir "$OUT_DIR"
|
||||
fi
|
||||
@@ -357,6 +407,10 @@ elif [ "$MODEL" = "dspark-support" ]; then
|
||||
echo
|
||||
echo "DSpark support downloaded. Enable it explicitly in greedy mode:"
|
||||
echo " ./ds4 --dspark -m ./ds4flash.gguf --mtp $OUT_DIR/$DSPARK_SUPPORT_FILE --temp 0"
|
||||
elif [ "$MODEL" = "laguna-dflash" ]; then
|
||||
echo
|
||||
echo "Laguna DFlash support downloaded. Enable it explicitly in greedy mode:"
|
||||
echo " ./ds4 -m <laguna-model.gguf> --dflash $OUT_DIR/$LAGUNA_DFLASH_FILE --temp 0"
|
||||
elif [ "$MODEL" = "pro-q4-layers00-30" ] || [ "$MODEL" = "pro-q4-layers31-output" ] || [ "$MODEL" = "pro-q4-split" ]; then
|
||||
echo
|
||||
echo "Downloaded PRO Q4 distributed split file(s). Use them with --layers,"
|
||||
|
||||
@@ -127,11 +127,14 @@ typedef struct {
|
||||
typedef struct {
|
||||
const char *model_path;
|
||||
const char *mtp_path;
|
||||
const char *dflash_path;
|
||||
ds4_backend backend;
|
||||
int n_threads;
|
||||
int context_size;
|
||||
uint32_t prefill_chunk;
|
||||
int mtp_draft_tokens;
|
||||
int dflash_draft_tokens;
|
||||
float dflash_p_min;
|
||||
float mtp_margin;
|
||||
float dspark_confidence_threshold;
|
||||
const char *directional_steering_file;
|
||||
@@ -150,6 +153,7 @@ typedef struct {
|
||||
bool glm_mtp_timing;
|
||||
bool dspark;
|
||||
bool dspark_strict;
|
||||
bool dflash_p_min_set;
|
||||
bool dspark_confidence_threshold_set;
|
||||
bool cuda_tensor_parallel;
|
||||
bool ssd_streaming;
|
||||
@@ -247,6 +251,10 @@ bool ds4_engine_glm_layer_payload_bytes(ds4_engine *e,
|
||||
* Pro and later shapes must use nonzero ids. */
|
||||
int ds4_engine_model_id(ds4_engine *e);
|
||||
bool ds4_engine_is_glm_dsa(ds4_engine *e);
|
||||
bool ds4_engine_is_laguna(ds4_engine *e);
|
||||
const char *ds4_engine_default_system_prompt(ds4_engine *e);
|
||||
void ds4_engine_sampling_defaults(ds4_engine *e, float *temperature,
|
||||
int *top_k, float *top_p, float *min_p);
|
||||
const char *ds4_backend_name(ds4_backend backend);
|
||||
bool ds4_think_mode_enabled(ds4_think_mode mode);
|
||||
const char *ds4_think_mode_name(ds4_think_mode mode);
|
||||
@@ -284,7 +292,6 @@ int ds4_engine_collect_imatrix(ds4_engine *e,
|
||||
void ds4_engine_dump_tokens(ds4_engine *e, const ds4_tokens *tokens);
|
||||
int ds4_dump_text_tokenization(const char *model_path, const char *text, FILE *fp);
|
||||
int ds4_engine_head_test(ds4_engine *e, const ds4_tokens *prompt);
|
||||
bool ds4_engine_is_glm_dsa(ds4_engine *e);
|
||||
int ds4_engine_first_token_test(ds4_engine *e, const ds4_tokens *prompt);
|
||||
int ds4_engine_metal_graph_test(ds4_engine *e, const ds4_tokens *prompt);
|
||||
int ds4_engine_metal_graph_full_test(ds4_engine *e, const ds4_tokens *prompt);
|
||||
@@ -307,6 +314,7 @@ void ds4_encode_chat_prompt(
|
||||
void ds4_chat_append_max_effort_prefix(ds4_engine *e, ds4_tokens *tokens);
|
||||
void ds4_chat_append_message(ds4_engine *e, ds4_tokens *tokens, const char *role, const char *content);
|
||||
void ds4_chat_append_assistant_prefix(ds4_engine *e, ds4_tokens *tokens, ds4_think_mode think_mode);
|
||||
void ds4_chat_append_assistant_end(ds4_engine *e, ds4_tokens *tokens);
|
||||
|
||||
char *ds4_token_text(ds4_engine *e, int token, size_t *len);
|
||||
int ds4_token_eos(ds4_engine *e);
|
||||
@@ -338,6 +346,10 @@ void ds4_session_set_display_progress(ds4_session *s, ds4_session_progress_fn fn
|
||||
* safe boundaries where the live checkpoint is either unchanged or represents a
|
||||
* valid token prefix, and returns DS4_SESSION_SYNC_INTERRUPTED when it stops. */
|
||||
void ds4_session_set_cancel(ds4_session *s, ds4_session_cancel_fn fn, void *ud);
|
||||
/* Enable optional speculative support-model state for the next sync/decode
|
||||
* sequence. Frontends disable it for sampling modes that cannot use a greedy
|
||||
* verifier, avoiding support-model work on every ordinary decode token. */
|
||||
void ds4_session_set_speculative_enabled(ds4_session *s, bool enabled);
|
||||
void ds4_session_report_progress(ds4_session *s, const char *event, int current, int total);
|
||||
/* Distributed coordinator sessions return 1 when the full layer route is
|
||||
* available, 0 when it is still incomplete, and -1 for a local API error. */
|
||||
|
||||
+429
-75
@@ -56,9 +56,11 @@ typedef struct {
|
||||
int n_predict;
|
||||
int ctx_size;
|
||||
float temperature;
|
||||
int top_k;
|
||||
float top_p;
|
||||
float min_p;
|
||||
bool temperature_set;
|
||||
bool top_k_set;
|
||||
bool top_p_set;
|
||||
bool min_p_set;
|
||||
uint64_t seed;
|
||||
@@ -234,6 +236,7 @@ typedef struct {
|
||||
typedef enum {
|
||||
AGENT_TOOL_SYNTAX_DSML,
|
||||
AGENT_TOOL_SYNTAX_GLM,
|
||||
AGENT_TOOL_SYNTAX_LAGUNA,
|
||||
} agent_tool_syntax;
|
||||
|
||||
typedef enum {
|
||||
@@ -347,18 +350,18 @@ static int agent_worker_sync_tokens(agent_worker *w, const ds4_tokens *tokens,
|
||||
static int agent_read_default_lines(agent_worker *w);
|
||||
|
||||
static agent_tool_syntax agent_tool_syntax_for_engine(ds4_engine *engine) {
|
||||
return ds4_engine_is_glm_dsa(engine) ? AGENT_TOOL_SYNTAX_GLM
|
||||
: AGENT_TOOL_SYNTAX_DSML;
|
||||
if (ds4_engine_is_glm_dsa(engine)) return AGENT_TOOL_SYNTAX_GLM;
|
||||
if (ds4_engine_is_laguna(engine)) return AGENT_TOOL_SYNTAX_LAGUNA;
|
||||
return AGENT_TOOL_SYNTAX_DSML;
|
||||
}
|
||||
|
||||
static bool agent_tool_syntax_assistant_turn_uses_eos(agent_tool_syntax syntax) {
|
||||
return syntax != AGENT_TOOL_SYNTAX_GLM;
|
||||
static bool agent_tool_syntax_is_tagged(agent_tool_syntax syntax) {
|
||||
return syntax == AGENT_TOOL_SYNTAX_GLM ||
|
||||
syntax == AGENT_TOOL_SYNTAX_LAGUNA;
|
||||
}
|
||||
|
||||
static void agent_worker_append_assistant_turn_end(agent_worker *w) {
|
||||
if (agent_tool_syntax_assistant_turn_uses_eos(
|
||||
agent_tool_syntax_for_engine(w->engine)))
|
||||
ds4_tokens_push(&w->transcript, ds4_token_eos(w->engine));
|
||||
ds4_chat_append_assistant_end(w->engine, &w->transcript);
|
||||
}
|
||||
|
||||
static int agent_worker_effective_ctx_size(const agent_worker *w) {
|
||||
@@ -525,9 +528,18 @@ static float parse_float_range(const char *s, const char *opt, float min, float
|
||||
|
||||
static ds4_backend parse_backend(const char *s) {
|
||||
if (!strcmp(s, "metal")) return DS4_BACKEND_METAL;
|
||||
#ifdef DS4_ROCM_BUILD
|
||||
if (!strcmp(s, "rocm")) return DS4_BACKEND_CUDA;
|
||||
#else
|
||||
if (!strcmp(s, "cuda")) return DS4_BACKEND_CUDA;
|
||||
#endif
|
||||
if (!strcmp(s, "cpu")) return DS4_BACKEND_CPU;
|
||||
fprintf(stderr, "ds4-agent: invalid backend: %s\n", s);
|
||||
#ifdef DS4_ROCM_BUILD
|
||||
fprintf(stderr, "ds4-agent: valid backends are: metal, rocm, cpu\n");
|
||||
#else
|
||||
fprintf(stderr, "ds4-agent: valid backends are: metal, cuda, cpu\n");
|
||||
#endif
|
||||
exit(2);
|
||||
}
|
||||
|
||||
@@ -565,6 +577,7 @@ static agent_config parse_options(int argc, char **argv) {
|
||||
.model_path = "ds4flash.gguf",
|
||||
.backend = default_backend(),
|
||||
.mtp_draft_tokens = 1,
|
||||
.dflash_draft_tokens = 0,
|
||||
.mtp_margin = 3.0f,
|
||||
},
|
||||
.gen = {
|
||||
@@ -618,6 +631,16 @@ static agent_config parse_options(int argc, char **argv) {
|
||||
c.engine.model_path = need_arg(&i, argc, argv, arg);
|
||||
} else if (!strcmp(arg, "--mtp")) {
|
||||
c.engine.mtp_path = need_arg(&i, argc, argv, arg);
|
||||
} else if (!strcmp(arg, "--dflash")) {
|
||||
c.engine.dflash_path = need_arg(&i, argc, argv, arg);
|
||||
} else if (!strcmp(arg, "--dflash-draft")) {
|
||||
c.engine.dflash_draft_tokens =
|
||||
parse_int(need_arg(&i, argc, argv, arg), arg);
|
||||
} else if (!strcmp(arg, "--dflash-p-min")) {
|
||||
c.engine.dflash_p_min =
|
||||
parse_float_range(
|
||||
need_arg(&i, argc, argv, arg), arg, 0.0f, 1.0f);
|
||||
c.engine.dflash_p_min_set = true;
|
||||
} else if (!strcmp(arg, "--mtp-draft")) {
|
||||
c.engine.mtp_draft_tokens = parse_int(need_arg(&i, argc, argv, arg), arg);
|
||||
} else if (!strcmp(arg, "--mtp-margin")) {
|
||||
@@ -644,6 +667,9 @@ static agent_config parse_options(int argc, char **argv) {
|
||||
} else if (!strcmp(arg, "--temp")) {
|
||||
c.gen.temperature = parse_float_range(need_arg(&i, argc, argv, arg), arg, 0.0f, 100.0f);
|
||||
c.gen.temperature_set = true;
|
||||
} else if (!strcmp(arg, "--top-k")) {
|
||||
c.gen.top_k = parse_nonnegative_int(need_arg(&i, argc, argv, arg), arg);
|
||||
c.gen.top_k_set = true;
|
||||
} else if (!strcmp(arg, "--top-p")) {
|
||||
c.gen.top_p = parse_float_range(need_arg(&i, argc, argv, arg), arg, 0.0f, 1.0f);
|
||||
c.gen.top_p_set = true;
|
||||
@@ -662,8 +688,13 @@ static agent_config parse_options(int argc, char **argv) {
|
||||
c.engine.backend = parse_backend(need_arg(&i, argc, argv, arg));
|
||||
} else if (!strcmp(arg, "--metal")) {
|
||||
c.engine.backend = DS4_BACKEND_METAL;
|
||||
#ifdef DS4_ROCM_BUILD
|
||||
} else if (!strcmp(arg, "--rocm")) {
|
||||
c.engine.backend = DS4_BACKEND_CUDA;
|
||||
#else
|
||||
} else if (!strcmp(arg, "--cuda")) {
|
||||
c.engine.backend = DS4_BACKEND_CUDA;
|
||||
#endif
|
||||
} else if (!strcmp(arg, "--gpu-vram")) {
|
||||
c.gpu_vram_arg = need_arg(&i, argc, argv, arg);
|
||||
} else if (!strcmp(arg, "--gpu-devices")) {
|
||||
@@ -766,11 +797,14 @@ static agent_config parse_options(int argc, char **argv) {
|
||||
static void agent_apply_model_sampling_defaults(
|
||||
ds4_engine *engine,
|
||||
agent_generation_options *gen) {
|
||||
if (!engine || !gen || !ds4_engine_is_glm_dsa(engine)) return;
|
||||
|
||||
if (!gen->temperature_set) gen->temperature = 1.0f;
|
||||
if (!gen->top_p_set) gen->top_p = 0.95f;
|
||||
if (!gen->min_p_set) gen->min_p = 0.0f;
|
||||
if (!engine || !gen) return;
|
||||
float temperature, top_p, min_p;
|
||||
int top_k;
|
||||
ds4_engine_sampling_defaults(engine, &temperature, &top_k, &top_p, &min_p);
|
||||
if (!gen->temperature_set) gen->temperature = temperature;
|
||||
if (!gen->top_k_set) gen->top_k = top_k;
|
||||
if (!gen->top_p_set) gen->top_p = top_p;
|
||||
if (!gen->min_p_set) gen->min_p = min_p;
|
||||
}
|
||||
|
||||
static ds4_think_mode effective_think_mode(const agent_config *cfg) {
|
||||
@@ -1089,9 +1123,48 @@ static char *agent_build_glm_tools_prompt(void) {
|
||||
return out;
|
||||
}
|
||||
|
||||
static const char agent_laguna_tools_prompt_intro[] =
|
||||
"You are a coding agent running in a local workspace. Use tools for local file and system work. "
|
||||
"Avoid printing large file contents or large code blocks as answers; create or edit files with tools, "
|
||||
"then summarize results briefly.\n\n"
|
||||
"### Tools\n\n"
|
||||
"You may call functions to assist with the user query.\n"
|
||||
"All available function signatures are listed below:\n"
|
||||
"<available_tools>\n";
|
||||
|
||||
static const char agent_laguna_tools_prompt_after_schemas[] =
|
||||
"</available_tools>\n\n"
|
||||
"For a function call, use exactly this format:\n"
|
||||
"<tool_call>{function-name}<arg_key>{argument-name}</arg_key>"
|
||||
"<arg_value>{argument-value}</arg_value></tool_call>\n\n"
|
||||
"Tool calls are not allowed inside <think></think>; finish thinking before emitting <tool_call>.\n\n"
|
||||
"# Rules\n\n"
|
||||
"- Use Laguna's native <tool_call>, <arg_key>, and <arg_value> tags.\n"
|
||||
"- read path alone returns a context-sized bounded chunk, not the whole file; for first looks at large files, prefer max_lines around 80-160.\n"
|
||||
"- If read says more lines are available, call more with count=<lines> to read the next chunk.\n"
|
||||
"- Use whole=true only when the user explicitly asks for the complete file contents or when bounded chunks are insufficient; add raw=true only when line numbers would corrupt the payload.\n"
|
||||
"- " AGENT_EDIT_TARGET_RULE "\n"
|
||||
"- Use edit with exact old text and replacement new text; old may contain one [upto] marker between unique anchors.\n"
|
||||
"- For long bash jobs, pass refresh_sec and then poll with bash_status or stop with bash_stop.\n"
|
||||
"- Preserve the current system configuration unless the user explicitly asks otherwise.\n";
|
||||
|
||||
static char *agent_build_laguna_tools_prompt(void) {
|
||||
size_t a = strlen(agent_laguna_tools_prompt_intro);
|
||||
size_t b = strlen(agent_glm_tool_schemas);
|
||||
size_t c = strlen(agent_laguna_tools_prompt_after_schemas);
|
||||
char *out = xmalloc(a + b + c + 1);
|
||||
memcpy(out, agent_laguna_tools_prompt_intro, a);
|
||||
memcpy(out + a, agent_glm_tool_schemas, b);
|
||||
memcpy(out + a + b, agent_laguna_tools_prompt_after_schemas, c + 1);
|
||||
return out;
|
||||
}
|
||||
|
||||
static char *agent_build_tools_prompt(ds4_engine *engine) {
|
||||
if (agent_tool_syntax_for_engine(engine) == AGENT_TOOL_SYNTAX_GLM)
|
||||
agent_tool_syntax syntax = agent_tool_syntax_for_engine(engine);
|
||||
if (syntax == AGENT_TOOL_SYNTAX_GLM)
|
||||
return agent_build_glm_tools_prompt();
|
||||
if (syntax == AGENT_TOOL_SYNTAX_LAGUNA)
|
||||
return agent_build_laguna_tools_prompt();
|
||||
return agent_build_dsml_tools_prompt();
|
||||
}
|
||||
|
||||
@@ -1108,6 +1181,16 @@ static const char agent_glm_syntax_reminder[] =
|
||||
"<tool_call>$TOOL_NAME<arg_key>$PARAMETER_NAME</arg_key>"
|
||||
"<arg_value>$PARAMETER_VALUE</arg_value></tool_call>\n";
|
||||
|
||||
static const char agent_laguna_syntax_reminder[] =
|
||||
"Laguna tool-call syntax reminder:\n"
|
||||
"<tool_call>$TOOL_NAME<arg_key>$PARAMETER_NAME</arg_key>"
|
||||
"<arg_value>$PARAMETER_VALUE</arg_value></tool_call>\n";
|
||||
|
||||
static const char *agent_tagged_syntax_reminder(agent_tool_syntax syntax) {
|
||||
return syntax == AGENT_TOOL_SYNTAX_LAGUNA ?
|
||||
agent_laguna_syntax_reminder : agent_glm_syntax_reminder;
|
||||
}
|
||||
|
||||
#define AGENT_SYSTEM_PROMPT_REMINDER_TOKENS 50000
|
||||
|
||||
static char *agent_build_system_prompt_reminder(ds4_engine *engine) {
|
||||
@@ -1129,7 +1212,7 @@ static void agent_append_system_prompt(ds4_engine *engine, ds4_tokens *tokens,
|
||||
* supplied -sys text: arbitrary user text containing <|User|>, <think>, or
|
||||
* |DSML| must remain plain content, not control tokens. */
|
||||
char *tools_prompt = agent_build_tools_prompt(engine);
|
||||
if (agent_tool_syntax_for_engine(engine) == AGENT_TOOL_SYNTAX_GLM)
|
||||
if (agent_tool_syntax_is_tagged(agent_tool_syntax_for_engine(engine)))
|
||||
ds4_chat_append_message(engine, tokens, "system", tools_prompt);
|
||||
else
|
||||
ds4_tokenize_rendered_chat(engine, tools_prompt, tokens);
|
||||
@@ -1182,7 +1265,8 @@ static void agent_worker_maybe_append_system_prompt_reminder(agent_worker *w) {
|
||||
agent_publish_system_status(w, "Re-injecting system prompt reminder...");
|
||||
agent_trace(w, "system prompt reminder injected at transcript=%d",
|
||||
w->transcript.len);
|
||||
if (agent_tool_syntax_for_engine(w->engine) == AGENT_TOOL_SYNTAX_GLM) {
|
||||
if (agent_tool_syntax_is_tagged(
|
||||
agent_tool_syntax_for_engine(w->engine))) {
|
||||
ds4_chat_append_message(w->engine, &w->transcript, "system", reminder);
|
||||
} else {
|
||||
ds4_tokenize_rendered_chat(w->engine, reminder, &w->transcript);
|
||||
@@ -1562,7 +1646,7 @@ static bool agent_glm_arg_value_close_tail(const char *tail, size_t len,
|
||||
static bool agent_tool_value_close_tail(agent_tool_syntax syntax,
|
||||
const char *tail, size_t len,
|
||||
bool *complete) {
|
||||
if (syntax == AGENT_TOOL_SYNTAX_GLM)
|
||||
if (agent_tool_syntax_is_tagged(syntax))
|
||||
return agent_glm_arg_value_close_tail(tail, len, complete);
|
||||
return agent_dsml_parameter_close_tail(tail, len, complete);
|
||||
}
|
||||
@@ -1634,7 +1718,7 @@ static void agent_glm_tool_parse(agent_dsml_parser *p) {
|
||||
const char *value_end = strstr(raw + p->param_value_start, arg_value_close);
|
||||
if (!value_end) {
|
||||
const char *call_end = strstr(raw + p->param_value_start, close);
|
||||
if (call_end) agent_dsml_set_error(p, "unterminated <arg_value> in GLM tool call");
|
||||
if (call_end) agent_dsml_set_error(p, "unterminated <arg_value> in tagged tool call");
|
||||
return;
|
||||
}
|
||||
agent_tool_call_add_arg(&p->current, p->param_name ? p->param_name : "",
|
||||
@@ -1678,13 +1762,13 @@ static void agent_glm_tool_parse(agent_dsml_parser *p) {
|
||||
if (agent_bytes_partial_prefix_at(lt, end, arg_key) ||
|
||||
agent_bytes_partial_prefix_at(lt, end, close))
|
||||
return;
|
||||
agent_dsml_set_error(p, "expected <arg_key> or </tool_call> in GLM tool call");
|
||||
agent_dsml_set_error(p, "expected <arg_key> or </tool_call> in tagged tool call");
|
||||
return;
|
||||
}
|
||||
const char *name_end = lt;
|
||||
agent_trim_span(&name_start, &name_end);
|
||||
if (name_start >= name_end) {
|
||||
agent_dsml_set_error(p, "GLM tool call without function name");
|
||||
agent_dsml_set_error(p, "tagged tool call without function name");
|
||||
return;
|
||||
}
|
||||
agent_tool_call_free(&p->current);
|
||||
@@ -1703,21 +1787,21 @@ static void agent_glm_tool_parse(agent_dsml_parser *p) {
|
||||
|
||||
if (!agent_bytes_starts_with(cur, end, arg_key)) {
|
||||
if (agent_bytes_partial_prefix_at(cur, end, arg_key)) return;
|
||||
agent_dsml_set_error(p, "expected <arg_key> in GLM tool call");
|
||||
agent_dsml_set_error(p, "expected <arg_key> in tagged tool call");
|
||||
return;
|
||||
}
|
||||
cur += sizeof(arg_key) - 1;
|
||||
const char *key_end_mut = strstr(cur, arg_key_close);
|
||||
if (!key_end_mut) {
|
||||
const char *call_end = strstr(cur, close);
|
||||
if (call_end) agent_dsml_set_error(p, "unterminated <arg_key> in GLM tool call");
|
||||
if (call_end) agent_dsml_set_error(p, "unterminated <arg_key> in tagged tool call");
|
||||
return;
|
||||
}
|
||||
const char *key_start = cur;
|
||||
const char *key_end = key_end_mut;
|
||||
agent_trim_span(&key_start, &key_end);
|
||||
if (key_start >= key_end) {
|
||||
agent_dsml_set_error(p, "empty <arg_key> in GLM tool call");
|
||||
agent_dsml_set_error(p, "empty <arg_key> in tagged tool call");
|
||||
return;
|
||||
}
|
||||
char *key = xstrndup(key_start, (size_t)(key_end - key_start));
|
||||
@@ -1729,7 +1813,7 @@ static void agent_glm_tool_parse(agent_dsml_parser *p) {
|
||||
return;
|
||||
}
|
||||
free(key);
|
||||
agent_dsml_set_error(p, "expected <arg_value> in GLM tool call");
|
||||
agent_dsml_set_error(p, "expected <arg_value> in tagged tool call");
|
||||
return;
|
||||
}
|
||||
cur += sizeof(arg_value) - 1;
|
||||
@@ -1746,7 +1830,7 @@ static void agent_glm_tool_parse(agent_dsml_parser *p) {
|
||||
static void agent_dsml_finish(agent_dsml_parser *p) {
|
||||
if (!p || p->state == AGENT_DSML_DONE || p->state == AGENT_DSML_ERROR)
|
||||
return;
|
||||
if (p->syntax != AGENT_TOOL_SYNTAX_GLM || !p->glm_after_call)
|
||||
if (!agent_tool_syntax_is_tagged(p->syntax) || !p->glm_after_call)
|
||||
return;
|
||||
|
||||
while (p->parse_pos < p->raw_len &&
|
||||
@@ -1764,7 +1848,7 @@ static void agent_dsml_finish(agent_dsml_parser *p) {
|
||||
* until enough bytes arrive, while malformed completed input switches to
|
||||
* AGENT_DSML_ERROR so the model gets a retryable tool error. */
|
||||
static void agent_dsml_parse(agent_dsml_parser *p) {
|
||||
if (p->syntax == AGENT_TOOL_SYNTAX_GLM) {
|
||||
if (agent_tool_syntax_is_tagged(p->syntax)) {
|
||||
agent_glm_tool_parse(p);
|
||||
return;
|
||||
}
|
||||
@@ -1847,7 +1931,7 @@ static void agent_dsml_parse(agent_dsml_parser *p) {
|
||||
}
|
||||
|
||||
static void agent_dsml_start(agent_dsml_parser *p) {
|
||||
const char *start = p->syntax == AGENT_TOOL_SYNTAX_GLM ?
|
||||
const char *start = agent_tool_syntax_is_tagged(p->syntax) ?
|
||||
"<tool_call>" : "<|DSML|tool_calls>";
|
||||
p->state = AGENT_DSML_STRUCTURAL;
|
||||
p->search_len = 0;
|
||||
@@ -1856,7 +1940,7 @@ static void agent_dsml_start(agent_dsml_parser *p) {
|
||||
}
|
||||
|
||||
static void agent_dsml_feed(agent_dsml_parser *p, const char *s, size_t n) {
|
||||
const char *start = p->syntax == AGENT_TOOL_SYNTAX_GLM ?
|
||||
const char *start = agent_tool_syntax_is_tagged(p->syntax) ?
|
||||
"<tool_call>" : "<|DSML|tool_calls>";
|
||||
const size_t start_len = strlen(start);
|
||||
if (p->state == AGENT_DSML_DONE || p->state == AGENT_DSML_ERROR) return;
|
||||
@@ -1883,6 +1967,40 @@ static void agent_dsml_feed(agent_dsml_parser *p, const char *s, size_t n) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Check a candidate token before it becomes part of a tagged tool call. Replay
|
||||
* is intentionally used only on ambiguous structural bytes, so the normal
|
||||
* generation path and parameter payloads pay no extra work. */
|
||||
static bool agent_tagged_structural_candidate_valid(
|
||||
const agent_dsml_parser *p, const char *text, size_t text_len) {
|
||||
if (!p || !text || !agent_tool_syntax_is_tagged(p->syntax) ||
|
||||
p->state != AGENT_DSML_STRUCTURAL || !p->raw)
|
||||
return true;
|
||||
|
||||
const bool candidate_has_tag = memchr(text, '<', text_len) != NULL;
|
||||
const bool pending_tag =
|
||||
p->parse_pos < p->raw_len &&
|
||||
memchr(p->raw + p->parse_pos, '<', p->raw_len - p->parse_pos) != NULL;
|
||||
if (!candidate_has_tag && !pending_tag) return true;
|
||||
|
||||
agent_dsml_parser probe = {
|
||||
.syntax = p->syntax,
|
||||
.state = AGENT_DSML_SEARCH,
|
||||
};
|
||||
agent_dsml_feed(&probe, p->raw, p->raw_len);
|
||||
if (probe.state != AGENT_DSML_ERROR)
|
||||
agent_dsml_feed(&probe, text, text_len);
|
||||
const bool valid = probe.state != AGENT_DSML_ERROR;
|
||||
agent_dsml_parser_free(&probe);
|
||||
return valid;
|
||||
}
|
||||
|
||||
static bool agent_tagged_structural_candidate_allowed(
|
||||
const agent_dsml_parser *p, const char *text, size_t text_len,
|
||||
bool stop_token) {
|
||||
return agent_tagged_structural_candidate_valid(p, text, text_len) &&
|
||||
(!stop_token || (p && p->glm_after_call));
|
||||
}
|
||||
|
||||
/* ============================================================================
|
||||
* Assistant Markdown Rendering
|
||||
* ============================================================================
|
||||
@@ -3574,7 +3692,8 @@ static void agent_stream_start_dsml(agent_stream_renderer *sr, bool ignored) {
|
||||
sr->dsml_start_len = 0;
|
||||
sr->post_think_gap = false;
|
||||
agent_trace(sr->renderer->worker, "%s tool start detected%s",
|
||||
sr->syntax == AGENT_TOOL_SYNTAX_GLM ? "glm" : "dsml",
|
||||
sr->syntax == AGENT_TOOL_SYNTAX_LAGUNA ? "laguna" :
|
||||
(sr->syntax == AGENT_TOOL_SYNTAX_GLM ? "glm" : "dsml"),
|
||||
ignored ? " inside thinking" : "");
|
||||
agent_dsml_start(sr->parser);
|
||||
if (!ignored) {
|
||||
@@ -3600,7 +3719,7 @@ static bool agent_stream_dsml_start_match(agent_tool_syntax syntax,
|
||||
const char *tail, size_t len,
|
||||
bool *complete,
|
||||
bool *implicit_invoke) {
|
||||
if (syntax == AGENT_TOOL_SYNTAX_GLM) {
|
||||
if (agent_tool_syntax_is_tagged(syntax)) {
|
||||
static const char glm_call[] = "<tool_call>";
|
||||
size_t form_len = sizeof(glm_call) - 1;
|
||||
*complete = false;
|
||||
@@ -3693,7 +3812,7 @@ static void agent_stream_note_plain_dsml_byte(agent_stream_renderer *sr,
|
||||
* can split "<|DSML|tool_calls>" across arbitrary tokens. */
|
||||
static void agent_stream_normal_byte(agent_stream_renderer *sr, char c) {
|
||||
static const char canonical_invoke[] = "<|DSML|invoke";
|
||||
const char *start = sr->syntax == AGENT_TOOL_SYNTAX_GLM ?
|
||||
const char *start = agent_tool_syntax_is_tagged(sr->syntax) ?
|
||||
"<tool_call>" : "<|DSML|tool_calls>";
|
||||
if (sr->parser->state == AGENT_DSML_ERROR) return;
|
||||
agent_stream_note_thinking_dsml_byte(sr, c);
|
||||
@@ -4381,7 +4500,8 @@ static void agent_worker_build_system_tokens(agent_worker *w, ds4_tokens *out) {
|
||||
if (agent_tool_syntax_for_engine(w->engine) == AGENT_TOOL_SYNTAX_GLM) {
|
||||
const char *effort = ds4_glm_reasoning_effort_text(think_mode);
|
||||
if (effort) ds4_chat_append_message(w->engine, out, "system", effort);
|
||||
} else if (w->cfg->gen.think_mode == DS4_THINK_MAX &&
|
||||
} else if (!ds4_engine_is_laguna(w->engine) &&
|
||||
w->cfg->gen.think_mode == DS4_THINK_MAX &&
|
||||
think_mode == DS4_THINK_MAX) {
|
||||
ds4_chat_append_max_effort_prefix(w->engine, out);
|
||||
}
|
||||
@@ -6903,6 +7023,51 @@ static void test_agent_glm_tool_parser_rejects_missing_value(void) {
|
||||
agent_dsml_parser_free(&p);
|
||||
}
|
||||
|
||||
static void test_agent_tagged_structural_candidate_guard(void) {
|
||||
agent_dsml_parser p = {
|
||||
.syntax = AGENT_TOOL_SYNTAX_LAGUNA,
|
||||
.state = AGENT_DSML_SEARCH,
|
||||
};
|
||||
const char *start = "<tool_call>bash";
|
||||
agent_dsml_feed(&p, start, strlen(start));
|
||||
AGENT_TEST_ASSERT(p.state == AGENT_DSML_STRUCTURAL);
|
||||
AGENT_TEST_ASSERT(agent_tagged_structural_candidate_valid(
|
||||
&p, "<arg_key>", strlen("<arg_key>")));
|
||||
AGENT_TEST_ASSERT(!agent_tagged_structural_candidate_valid(
|
||||
&p, "<e", strlen("<e")));
|
||||
AGENT_TEST_ASSERT(!agent_tagged_structural_candidate_allowed(
|
||||
&p, "", 0, true));
|
||||
|
||||
const char *key = "<arg_key>command</arg_key>";
|
||||
agent_dsml_feed(&p, key, strlen(key));
|
||||
AGENT_TEST_ASSERT(p.state == AGENT_DSML_STRUCTURAL);
|
||||
AGENT_TEST_ASSERT(agent_tagged_structural_candidate_valid(
|
||||
&p, "<arg_value>", strlen("<arg_value>")));
|
||||
AGENT_TEST_ASSERT(!agent_tagged_structural_candidate_valid(
|
||||
&p, "<v", strlen("<v")));
|
||||
|
||||
agent_dsml_feed(&p, "<v", strlen("<v"));
|
||||
AGENT_TEST_ASSERT(p.state == AGENT_DSML_ERROR);
|
||||
AGENT_TEST_ASSERT(strstr(p.error, "tagged tool call") != NULL);
|
||||
AGENT_TEST_ASSERT(strstr(agent_tagged_syntax_reminder(
|
||||
AGENT_TOOL_SYNTAX_LAGUNA), "Laguna") != NULL);
|
||||
agent_dsml_parser_free(&p);
|
||||
|
||||
agent_dsml_parser complete = {
|
||||
.syntax = AGENT_TOOL_SYNTAX_LAGUNA,
|
||||
.state = AGENT_DSML_SEARCH,
|
||||
};
|
||||
const char *call =
|
||||
"<tool_call>list<arg_key>path</arg_key><arg_value>.</arg_value>"
|
||||
"</tool_call>";
|
||||
agent_dsml_feed(&complete, call, strlen(call));
|
||||
AGENT_TEST_ASSERT(complete.state == AGENT_DSML_STRUCTURAL);
|
||||
AGENT_TEST_ASSERT(complete.glm_after_call);
|
||||
AGENT_TEST_ASSERT(agent_tagged_structural_candidate_allowed(
|
||||
&complete, "</assistant>", strlen("</assistant>"), true));
|
||||
agent_dsml_parser_free(&complete);
|
||||
}
|
||||
|
||||
static void test_agent_glm_tools_prompt_is_native(void) {
|
||||
char *prompt = agent_build_glm_tools_prompt();
|
||||
|
||||
@@ -6919,10 +7084,9 @@ static void test_agent_glm_tools_prompt_is_native(void) {
|
||||
}
|
||||
|
||||
static void test_agent_glm_template_policy(void) {
|
||||
AGENT_TEST_ASSERT(!agent_tool_syntax_assistant_turn_uses_eos(
|
||||
AGENT_TOOL_SYNTAX_GLM));
|
||||
AGENT_TEST_ASSERT(agent_tool_syntax_assistant_turn_uses_eos(
|
||||
AGENT_TOOL_SYNTAX_DSML));
|
||||
AGENT_TEST_ASSERT(agent_tool_syntax_is_tagged(AGENT_TOOL_SYNTAX_GLM));
|
||||
AGENT_TEST_ASSERT(agent_tool_syntax_is_tagged(AGENT_TOOL_SYNTAX_LAGUNA));
|
||||
AGENT_TEST_ASSERT(!agent_tool_syntax_is_tagged(AGENT_TOOL_SYNTAX_DSML));
|
||||
AGENT_TEST_ASSERT(!strcmp(ds4_glm_reasoning_effort_text(DS4_THINK_HIGH),
|
||||
"Reasoning Effort: High"));
|
||||
AGENT_TEST_ASSERT(!strcmp(ds4_glm_reasoning_effort_text(DS4_THINK_MAX),
|
||||
@@ -6949,10 +7113,21 @@ static void test_agent_read_default_lines_follow_context(void) {
|
||||
AGENT_READ_DEFAULT_LINES_LARGE);
|
||||
}
|
||||
|
||||
static void test_agent_backend_name_matches_build(void) {
|
||||
AGENT_TEST_ASSERT(parse_backend("metal") == DS4_BACKEND_METAL);
|
||||
AGENT_TEST_ASSERT(parse_backend("cpu") == DS4_BACKEND_CPU);
|
||||
#ifdef DS4_ROCM_BUILD
|
||||
AGENT_TEST_ASSERT(parse_backend("rocm") == DS4_BACKEND_CUDA);
|
||||
#else
|
||||
AGENT_TEST_ASSERT(parse_backend("cuda") == DS4_BACKEND_CUDA);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void ds4_agent_unit_tests_run(void) {
|
||||
test_agent_edit_upto_tail_newline_is_not_part_of_anchor();
|
||||
test_agent_edit_upto_requires_tail_after_newline_strip();
|
||||
test_agent_read_default_lines_follow_context();
|
||||
test_agent_backend_name_matches_build();
|
||||
test_agent_glm_template_policy();
|
||||
test_agent_glm_tools_prompt_is_native();
|
||||
test_agent_glm_tool_parser_single_arg();
|
||||
@@ -6964,6 +7139,7 @@ static void ds4_agent_unit_tests_run(void) {
|
||||
test_agent_glm_stream_greedy_sampling_boundaries();
|
||||
test_agent_dsml_stream_tool_call_chunked();
|
||||
test_agent_glm_tool_parser_rejects_missing_value();
|
||||
test_agent_tagged_structural_candidate_guard();
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -8291,13 +8467,14 @@ static bool agent_worker_compact_if_needed(agent_worker *w, const char *reason,
|
||||
return agent_worker_compact(w, reason, err, err_len);
|
||||
}
|
||||
|
||||
static int worker_accept_generated_token(agent_worker *w,
|
||||
int token,
|
||||
int *generated,
|
||||
double t0,
|
||||
agent_stream_renderer *stream,
|
||||
char *err,
|
||||
size_t err_len) {
|
||||
/* Publish a token whose target-model evaluation has already been committed.
|
||||
* Speculative verification advances several KV rows at once, so those tokens
|
||||
* must update the transcript and renderer without evaluating them again. */
|
||||
static void worker_publish_generated_token(agent_worker *w,
|
||||
int token,
|
||||
int *generated,
|
||||
double t0,
|
||||
agent_stream_renderer *stream) {
|
||||
ds4_tokens_push(&w->transcript, token);
|
||||
|
||||
size_t text_len = 0;
|
||||
@@ -8307,17 +8484,26 @@ static int worker_accept_generated_token(agent_worker *w,
|
||||
free(text);
|
||||
(*generated)++;
|
||||
|
||||
if (ds4_session_eval(w->session, token, err, err_len) != 0) {
|
||||
ds4_session_invalidate(w->session);
|
||||
return 1;
|
||||
}
|
||||
|
||||
double dt = now_sec() - t0;
|
||||
pthread_mutex_lock(&w->mu);
|
||||
w->status.generated = *generated;
|
||||
w->status.gen_tps = dt > 0.0 ? (double)*generated / dt : 0.0;
|
||||
agent_wake_locked(w);
|
||||
pthread_mutex_unlock(&w->mu);
|
||||
}
|
||||
|
||||
static int worker_accept_generated_token(agent_worker *w,
|
||||
int token,
|
||||
int *generated,
|
||||
double t0,
|
||||
agent_stream_renderer *stream,
|
||||
char *err,
|
||||
size_t err_len) {
|
||||
worker_publish_generated_token(w, token, generated, t0, stream);
|
||||
if (ds4_session_eval(w->session, token, err, err_len) != 0) {
|
||||
ds4_session_invalidate(w->session);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -8387,12 +8573,77 @@ static int worker_sample_with_mode(agent_worker *w, const agent_config *cfg,
|
||||
bool greedy, uint64_t *rng) {
|
||||
return ds4_session_sample(w->session,
|
||||
greedy ? 0.0f : cfg->gen.temperature,
|
||||
0,
|
||||
greedy ? 0 : cfg->gen.top_k,
|
||||
greedy ? 1.0f : cfg->gen.top_p,
|
||||
greedy ? 0.0f : cfg->gen.min_p,
|
||||
rng);
|
||||
}
|
||||
|
||||
/* Greedy structural decoding is normally enough, but a quantized model can
|
||||
* occasionally rank a malformed tag fragment first. Select the highest-ranked
|
||||
* token that keeps the tagged grammar viable before committing it to the KV
|
||||
* cache. The top-k scan runs only after the original argmax is proven invalid. */
|
||||
static int worker_repair_tagged_structural_token(
|
||||
agent_worker *w, agent_stream_renderer *stream,
|
||||
ds4_think_mode think_mode, int token) {
|
||||
if (!stream || !stream->dsml_active || !stream->parser ||
|
||||
!agent_tool_syntax_is_tagged(stream->syntax) ||
|
||||
stream->parser->state != AGENT_DSML_STRUCTURAL)
|
||||
return token;
|
||||
|
||||
size_t text_len = 0;
|
||||
char *text = ds4_token_text(w->engine, token, &text_len);
|
||||
const bool stop =
|
||||
ds4_token_is_stop_for_think_mode(w->engine, token, think_mode);
|
||||
if (agent_tagged_structural_candidate_allowed(
|
||||
stream->parser, text, text_len, stop)) {
|
||||
free(text);
|
||||
return token;
|
||||
}
|
||||
|
||||
enum { REPAIR_CANDIDATES = 64 };
|
||||
ds4_token_score scores[REPAIR_CANDIDATES];
|
||||
const int count =
|
||||
ds4_session_top_logprobs(w->session, scores, REPAIR_CANDIDATES);
|
||||
for (int i = 0; i < count; i++) {
|
||||
const int candidate = scores[i].id;
|
||||
if (candidate < 0)
|
||||
continue;
|
||||
const bool candidate_stop =
|
||||
ds4_token_is_stop_for_think_mode(w->engine, candidate, think_mode);
|
||||
|
||||
size_t candidate_len = 0;
|
||||
char *candidate_text =
|
||||
ds4_token_text(w->engine, candidate, &candidate_len);
|
||||
const bool candidate_valid = agent_tagged_structural_candidate_allowed(
|
||||
stream->parser, candidate_text, candidate_len, candidate_stop);
|
||||
if (candidate_valid) {
|
||||
agent_trace(w,
|
||||
"%s structural token repair rejected=%d accepted=%d rank=%d "
|
||||
"rejected_text=%.*s accepted_text=%.*s",
|
||||
stream->syntax == AGENT_TOOL_SYNTAX_LAGUNA ?
|
||||
"laguna" : "glm",
|
||||
token,
|
||||
candidate,
|
||||
i + 1,
|
||||
(int)(text_len > 48 ? 48 : text_len),
|
||||
text ? text : "",
|
||||
(int)(candidate_len > 48 ? 48 : candidate_len),
|
||||
candidate_text ? candidate_text : "");
|
||||
free(candidate_text);
|
||||
free(text);
|
||||
return candidate;
|
||||
}
|
||||
free(candidate_text);
|
||||
}
|
||||
|
||||
agent_trace(w, "%s structural token repair found no valid top-%d alternative",
|
||||
stream->syntax == AGENT_TOOL_SYNTAX_LAGUNA ? "laguna" : "glm",
|
||||
REPAIR_CANDIDATES);
|
||||
free(text);
|
||||
return token;
|
||||
}
|
||||
|
||||
static void worker_set_greedy_sampling(agent_worker *w, bool greedy) {
|
||||
pthread_mutex_lock(&w->mu);
|
||||
if (w->status.greedy_sampling != greedy) {
|
||||
@@ -8561,19 +8812,28 @@ static int worker_run_turn(agent_worker *w, const char *user_text) {
|
||||
agent_wake_locked(w);
|
||||
pthread_mutex_unlock(&w->mu);
|
||||
|
||||
const bool speculative_argmax =
|
||||
cfg->gen.temperature <= 0.0f &&
|
||||
ds4_engine_mtp_draft_tokens(w->engine) > 1 &&
|
||||
getenv("DS4_MTP_SPEC_DISABLE") == NULL;
|
||||
bool status_greedy_sampling = false;
|
||||
while (generated < max_tokens && !worker_should_interrupt(w)) {
|
||||
worker_apply_pending_power(w);
|
||||
bool stop_from_speculation = false;
|
||||
bool greedy_sampling = agent_stream_wants_greedy_sampling(&stream);
|
||||
if (greedy_sampling != status_greedy_sampling) {
|
||||
worker_set_greedy_sampling(w, greedy_sampling);
|
||||
status_greedy_sampling = greedy_sampling;
|
||||
}
|
||||
int token = worker_sample_with_mode(w, cfg, greedy_sampling, &rng);
|
||||
if (greedy_sampling) {
|
||||
token = worker_repair_tagged_structural_token(
|
||||
w, &stream, think_mode, token);
|
||||
}
|
||||
if (ds4_token_is_stop_for_think_mode(w->engine, token, think_mode)) {
|
||||
if (tool_syntax == AGENT_TOOL_SYNTAX_GLM &&
|
||||
if (agent_tool_syntax_is_tagged(tool_syntax) &&
|
||||
token != ds4_token_eos(w->engine)) {
|
||||
agent_trace(w, "glm assistant generation stopped before control token id=%d", token);
|
||||
agent_trace(w, "tagged assistant generation stopped before control token id=%d", token);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -8595,8 +8855,66 @@ static int worker_run_turn(agent_worker *w, const char *user_text) {
|
||||
}
|
||||
} else {
|
||||
free(text);
|
||||
if (worker_accept_generated_token(w, token, &generated, t0,
|
||||
&stream, err, sizeof(err)) != 0) {
|
||||
/* Once a tool stanza is in progress, retain token-at-a-time
|
||||
* decoding so structural repair and edit-old auto-[upto] can
|
||||
* inspect each token before it enters the KV cache. A batch
|
||||
* may discover the opening marker; the remaining verified
|
||||
* tokens are safe to render, then subsequent cycles switch to
|
||||
* the sequential path. */
|
||||
const bool can_speculate =
|
||||
speculative_argmax &&
|
||||
!stream.dsml_active &&
|
||||
stream.dsml_start_len == 0;
|
||||
if (can_speculate) {
|
||||
int toks[17];
|
||||
const int ntok = ds4_session_eval_speculative_argmax(
|
||||
w->session,
|
||||
token,
|
||||
max_tokens - generated,
|
||||
ds4_token_eos(w->engine),
|
||||
toks,
|
||||
(int)(sizeof(toks) / sizeof(toks[0])),
|
||||
err,
|
||||
sizeof(err));
|
||||
if (ntok < 0) {
|
||||
agent_dsml_parser_free(&dsml);
|
||||
agent_set_error(w, err[0] ? err : "DFlash decode failed");
|
||||
return 1;
|
||||
}
|
||||
|
||||
bool consumed_all = true;
|
||||
for (int i = 0; i < ntok; i++) {
|
||||
if (ds4_token_is_stop_for_think_mode(
|
||||
w->engine, toks[i], think_mode)) {
|
||||
consumed_all = false;
|
||||
stop_from_speculation = true;
|
||||
break;
|
||||
}
|
||||
worker_publish_generated_token(
|
||||
w, toks[i], &generated, t0, &stream);
|
||||
|
||||
const bool terminal =
|
||||
dsml.state == AGENT_DSML_DONE ||
|
||||
dsml.state == AGENT_DSML_ERROR ||
|
||||
stream.tool_preflight_error ||
|
||||
stream.dsml_in_think ||
|
||||
generated >= max_tokens ||
|
||||
worker_should_interrupt(w);
|
||||
if (terminal) {
|
||||
consumed_all = i + 1 == ntok;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!consumed_all) {
|
||||
/* Verification may have committed tokens after a stop
|
||||
* marker or completed tool stanza. They were never
|
||||
* published into the transcript, so force the next
|
||||
* prompt sync to rebuild from the visible prefix. */
|
||||
ds4_session_invalidate(w->session);
|
||||
}
|
||||
} else if (worker_accept_generated_token(
|
||||
w, token, &generated, t0,
|
||||
&stream, err, sizeof(err)) != 0) {
|
||||
agent_dsml_parser_free(&dsml);
|
||||
agent_set_error(w, err);
|
||||
return 1;
|
||||
@@ -8609,6 +8927,9 @@ static int worker_run_turn(agent_worker *w, const char *user_text) {
|
||||
status_greedy_sampling = greedy_sampling;
|
||||
}
|
||||
|
||||
if (stop_from_speculation) {
|
||||
break;
|
||||
}
|
||||
if (dsml.state == AGENT_DSML_DONE) {
|
||||
got_tool = true;
|
||||
break;
|
||||
@@ -8656,8 +8977,8 @@ static int worker_run_turn(agent_worker *w, const char *user_text) {
|
||||
{
|
||||
malformed_tool = true;
|
||||
snprintf(dsml.error, sizeof(dsml.error),
|
||||
tool_syntax == AGENT_TOOL_SYNTAX_GLM ?
|
||||
"incomplete GLM tool call" :
|
||||
agent_tool_syntax_is_tagged(tool_syntax) ?
|
||||
"incomplete tagged tool call" :
|
||||
"incomplete DSML tool call");
|
||||
}
|
||||
|
||||
@@ -8680,13 +9001,13 @@ static int worker_run_turn(agent_worker *w, const char *user_text) {
|
||||
tool_result = agent_buf_take(&b);
|
||||
} else if (malformed_tool) {
|
||||
agent_buf b = {0};
|
||||
agent_buf_puts(&b, tool_syntax == AGENT_TOOL_SYNTAX_GLM ?
|
||||
"Tool error: invalid GLM tool call: " :
|
||||
agent_buf_puts(&b, agent_tool_syntax_is_tagged(tool_syntax) ?
|
||||
"Tool error: invalid tagged tool call: " :
|
||||
"Tool error: invalid DSML tool call: ");
|
||||
agent_buf_puts(&b, dsml.error[0] ? dsml.error : "parse error");
|
||||
agent_buf_puts(&b, "\n");
|
||||
agent_buf_puts(&b, tool_syntax == AGENT_TOOL_SYNTAX_GLM ?
|
||||
agent_glm_syntax_reminder :
|
||||
agent_buf_puts(&b, agent_tool_syntax_is_tagged(tool_syntax) ?
|
||||
agent_tagged_syntax_reminder(tool_syntax) :
|
||||
agent_dsml_syntax_reminder);
|
||||
tool_result = agent_buf_take(&b);
|
||||
} else {
|
||||
@@ -8806,37 +9127,66 @@ static int worker_run_raw_prompt(agent_worker *w, const char *user_text) {
|
||||
agent_wake_locked(w);
|
||||
pthread_mutex_unlock(&w->mu);
|
||||
|
||||
const bool speculative_argmax =
|
||||
cfg->gen.temperature <= 0.0f &&
|
||||
ds4_engine_mtp_draft_tokens(w->engine) > 1 &&
|
||||
getenv("DS4_MTP_SPEC_DISABLE") == NULL;
|
||||
while (generated < max_tokens && !worker_should_interrupt(w)) {
|
||||
int token = ds4_session_sample(w->session,
|
||||
cfg->gen.temperature,
|
||||
0,
|
||||
cfg->gen.top_k,
|
||||
cfg->gen.top_p,
|
||||
cfg->gen.min_p,
|
||||
&rng);
|
||||
if (ds4_token_is_stop(w->engine, token)) break;
|
||||
|
||||
size_t text_len = 0;
|
||||
char *text = ds4_token_text(w->engine, token, &text_len);
|
||||
agent_trace_token(w, token, text, text_len, generated + 1);
|
||||
|
||||
if (ds4_session_eval(w->session, token, err, sizeof(err)) != 0) {
|
||||
free(text);
|
||||
int toks[17];
|
||||
int ntok = 1;
|
||||
toks[0] = token;
|
||||
if (speculative_argmax) {
|
||||
ntok = ds4_session_eval_speculative_argmax(
|
||||
w->session,
|
||||
token,
|
||||
max_tokens - generated,
|
||||
ds4_token_eos(w->engine),
|
||||
toks,
|
||||
(int)(sizeof(toks) / sizeof(toks[0])),
|
||||
err,
|
||||
sizeof(err));
|
||||
} else if (ds4_session_eval(
|
||||
w->session, token, err, sizeof(err)) != 0) {
|
||||
ntok = -1;
|
||||
}
|
||||
if (ntok < 0) {
|
||||
agent_set_error(w, err[0] ? err : "raw decode failed");
|
||||
ds4_tokens_free(&prompt);
|
||||
return 1;
|
||||
}
|
||||
|
||||
ds4_tokens_push(&w->transcript, token);
|
||||
agent_publish(w, text, text_len);
|
||||
free(text);
|
||||
generated++;
|
||||
bool stop = false;
|
||||
for (int i = 0; i < ntok; i++) {
|
||||
token = toks[i];
|
||||
if (ds4_token_is_stop(w->engine, token)) {
|
||||
ds4_session_invalidate(w->session);
|
||||
stop = true;
|
||||
break;
|
||||
}
|
||||
size_t text_len = 0;
|
||||
char *text = ds4_token_text(w->engine, token, &text_len);
|
||||
agent_trace_token(w, token, text, text_len, generated + 1);
|
||||
ds4_tokens_push(&w->transcript, token);
|
||||
agent_publish(w, text, text_len);
|
||||
free(text);
|
||||
generated++;
|
||||
|
||||
double dt = now_sec() - t0;
|
||||
pthread_mutex_lock(&w->mu);
|
||||
w->status.generated = generated;
|
||||
w->status.gen_tps = dt > 0.0 ? (double)generated / dt : 0.0;
|
||||
agent_wake_locked(w);
|
||||
pthread_mutex_unlock(&w->mu);
|
||||
double dt = now_sec() - t0;
|
||||
pthread_mutex_lock(&w->mu);
|
||||
w->status.generated = generated;
|
||||
w->status.gen_tps = dt > 0.0 ? (double)generated / dt : 0.0;
|
||||
agent_wake_locked(w);
|
||||
pthread_mutex_unlock(&w->mu);
|
||||
}
|
||||
if (stop) break;
|
||||
}
|
||||
|
||||
if (worker_should_interrupt(w)) {
|
||||
@@ -10341,6 +10691,10 @@ static int agent_worker_init(agent_worker *w, ds4_engine *engine, agent_config *
|
||||
fprintf(stderr, "ds4-agent: session backend is required\n");
|
||||
return -1;
|
||||
}
|
||||
ds4_session_set_speculative_enabled(
|
||||
w->session,
|
||||
cfg->gen.temperature <= 0.0f &&
|
||||
getenv("DS4_MTP_SPEC_DISABLE") == NULL);
|
||||
w->cache_dir = agent_default_cache_dir();
|
||||
if (!agent_mkdir_p(w->cache_dir)) {
|
||||
fprintf(stderr, "ds4-agent: failed to create %s: %s\n",
|
||||
|
||||
@@ -61,13 +61,16 @@ static bool cli_greedy_argmax_requested(bool speculative_requested) {
|
||||
typedef struct {
|
||||
const char *prompt;
|
||||
const char *system;
|
||||
bool system_set;
|
||||
bool raw_prompt;
|
||||
int n_predict;
|
||||
int ctx_size;
|
||||
float temperature;
|
||||
int top_k;
|
||||
float top_p;
|
||||
float min_p;
|
||||
bool temperature_set;
|
||||
bool top_k_set;
|
||||
bool top_p_set;
|
||||
bool min_p_set;
|
||||
uint64_t seed;
|
||||
@@ -356,6 +359,7 @@ static void cli_prefill_progress_cb(void *ud, const char *event, int current, in
|
||||
|
||||
static bool is_rendered_chat_prompt(const char *prompt) {
|
||||
static const char *prefixes[] = {
|
||||
"〈|EOS|〉",
|
||||
"<|begin▁of▁sentence|>",
|
||||
"<|User|>",
|
||||
"[gMASK]",
|
||||
@@ -511,11 +515,14 @@ static void build_prompt(ds4_engine *engine, const cli_generation_options *gen,
|
||||
static void cli_apply_model_sampling_defaults(
|
||||
ds4_engine *engine,
|
||||
cli_generation_options *gen) {
|
||||
if (!engine || !gen || !ds4_engine_is_glm_dsa(engine)) return;
|
||||
|
||||
if (!gen->temperature_set) gen->temperature = 1.0f;
|
||||
if (!gen->top_p_set) gen->top_p = 0.95f;
|
||||
if (!gen->min_p_set) gen->min_p = 0.0f;
|
||||
if (!engine || !gen) return;
|
||||
float temperature, top_p, min_p;
|
||||
int top_k;
|
||||
ds4_engine_sampling_defaults(engine, &temperature, &top_k, &top_p, &min_p);
|
||||
if (!gen->temperature_set) gen->temperature = temperature;
|
||||
if (!gen->top_k_set) gen->top_k = top_k;
|
||||
if (!gen->top_p_set) gen->top_p = top_p;
|
||||
if (!gen->min_p_set) gen->min_p = min_p;
|
||||
}
|
||||
|
||||
static int run_sampled_generation(ds4_engine *engine, const cli_config *cfg, const ds4_tokens *prompt) {
|
||||
@@ -531,6 +538,10 @@ static int run_sampled_generation(ds4_engine *engine, const cli_config *cfg, con
|
||||
/* Pay the one-time first-submission GPU cost before the prefill timer
|
||||
* starts (matches the TP worker's startup warmup). */
|
||||
ds4_session_gpu_warmup(session);
|
||||
ds4_session_set_speculative_enabled(
|
||||
session,
|
||||
cfg->gen.temperature <= 0.0f &&
|
||||
getenv("DS4_MTP_SPEC_DISABLE") == NULL);
|
||||
|
||||
char err[160];
|
||||
ds4_think_mode think_mode = cli_effective_think_mode(&cfg->gen);
|
||||
@@ -590,7 +601,7 @@ static int run_sampled_generation(ds4_engine *engine, const cli_config *cfg, con
|
||||
token = greedy_next;
|
||||
have_greedy_next = false;
|
||||
} else {
|
||||
token = ds4_session_sample(session, cfg->gen.temperature, 0,
|
||||
token = ds4_session_sample(session, cfg->gen.temperature, cfg->gen.top_k,
|
||||
cfg->gen.top_p, cfg->gen.min_p, &rng);
|
||||
}
|
||||
if (ds4_token_is_stop_for_think_mode(engine, token, think_mode)) break;
|
||||
@@ -1344,7 +1355,7 @@ static void repl_chat_build_think_prefix(ds4_engine *engine,
|
||||
if (ds4_engine_is_glm_dsa(engine)) {
|
||||
const char *effort = repl_glm_reasoning_effort_text(mode);
|
||||
if (effort) ds4_chat_append_message(engine, prefix, "system", effort);
|
||||
} else if (mode == DS4_THINK_MAX) {
|
||||
} else if (!ds4_engine_is_laguna(engine) && mode == DS4_THINK_MAX) {
|
||||
ds4_chat_append_max_effort_prefix(engine, prefix);
|
||||
}
|
||||
}
|
||||
@@ -1411,10 +1422,6 @@ static int repl_chat_set_ctx(ds4_engine *engine, repl_chat *chat, int ctx_size)
|
||||
return repl_chat_create_session(engine, chat, ctx_size);
|
||||
}
|
||||
|
||||
static bool repl_chat_assistant_turn_uses_eos(ds4_engine *engine) {
|
||||
return !ds4_engine_is_glm_dsa(engine);
|
||||
}
|
||||
|
||||
/* Run one interactive turn. The transcript is tentatively extended with user
|
||||
* and assistant markers, then ds4_session_sync() decides whether this is a KV
|
||||
* continuation. If prompt processing fails, the transcript rolls back before
|
||||
@@ -1425,6 +1432,10 @@ static int run_chat_turn(ds4_engine *engine, cli_config *cfg, repl_chat *chat, c
|
||||
return 1;
|
||||
}
|
||||
|
||||
ds4_session_set_speculative_enabled(
|
||||
chat->session,
|
||||
cfg->gen.temperature <= 0.0f &&
|
||||
getenv("DS4_MTP_SPEC_DISABLE") == NULL);
|
||||
ds4_think_mode think_mode = ds4_think_mode_for_context(cfg->gen.think_mode,
|
||||
chat->ctx_size);
|
||||
repl_chat_apply_think_prefix(engine, chat, think_mode);
|
||||
@@ -1496,7 +1507,7 @@ static int run_chat_turn(ds4_engine *engine, cli_config *cfg, repl_chat *chat, c
|
||||
} else {
|
||||
token = ds4_session_sample(chat->session,
|
||||
cfg->gen.temperature,
|
||||
0,
|
||||
cfg->gen.top_k,
|
||||
cfg->gen.top_p,
|
||||
cfg->gen.min_p,
|
||||
&rng);
|
||||
@@ -1566,8 +1577,8 @@ static int run_chat_turn(ds4_engine *engine, cli_config *cfg, repl_chat *chat, c
|
||||
if (interrupted && generated == 0) {
|
||||
chat->transcript.len = rollback_len;
|
||||
ds4_session_invalidate(chat->session);
|
||||
} else if (repl_chat_assistant_turn_uses_eos(engine)) {
|
||||
ds4_tokens_push(&chat->transcript, ds4_token_eos(engine));
|
||||
} else {
|
||||
ds4_chat_append_assistant_end(engine, &chat->transcript);
|
||||
}
|
||||
|
||||
const double prefill_s = t_prefill1 - t_prefill0;
|
||||
@@ -1760,11 +1771,12 @@ static cli_config parse_options(int argc, char **argv) {
|
||||
.model_path = "ds4flash.gguf",
|
||||
.backend = default_backend(),
|
||||
.mtp_draft_tokens = 1,
|
||||
.dflash_draft_tokens = 0,
|
||||
.mtp_margin = 3.0f,
|
||||
},
|
||||
.gen = {
|
||||
.prompt = NULL,
|
||||
.system = "You are a helpful assistant",
|
||||
.system = NULL,
|
||||
.n_predict = 50000,
|
||||
.ctx_size = 32768,
|
||||
.temperature = DS4_DEFAULT_TEMPERATURE,
|
||||
@@ -1833,12 +1845,23 @@ static cli_config parse_options(int argc, char **argv) {
|
||||
c.gen.prompt = c.prompt_owned;
|
||||
} else if (!strcmp(arg, "-sys") || !strcmp(arg, "--system")) {
|
||||
c.gen.system = need_arg(&i, argc, argv, arg);
|
||||
c.gen.system_set = true;
|
||||
} else if (!strcmp(arg, "--raw") || !strcmp(arg, "--raw-prompt")) {
|
||||
c.gen.raw_prompt = true;
|
||||
} else if (!strcmp(arg, "-m") || !strcmp(arg, "--model")) {
|
||||
c.engine.model_path = need_arg(&i, argc, argv, arg);
|
||||
} else if (!strcmp(arg, "--mtp")) {
|
||||
c.engine.mtp_path = need_arg(&i, argc, argv, arg);
|
||||
} else if (!strcmp(arg, "--dflash")) {
|
||||
c.engine.dflash_path = need_arg(&i, argc, argv, arg);
|
||||
} else if (!strcmp(arg, "--dflash-draft")) {
|
||||
c.engine.dflash_draft_tokens =
|
||||
parse_int(need_arg(&i, argc, argv, arg), arg);
|
||||
} else if (!strcmp(arg, "--dflash-p-min")) {
|
||||
c.engine.dflash_p_min =
|
||||
parse_float_range(
|
||||
need_arg(&i, argc, argv, arg), arg, 0.0f, 1.0f);
|
||||
c.engine.dflash_p_min_set = true;
|
||||
} else if (!strcmp(arg, "--mtp-draft")) {
|
||||
c.engine.mtp_draft_tokens = parse_int(need_arg(&i, argc, argv, arg), arg);
|
||||
} else if (!strcmp(arg, "--mtp-margin")) {
|
||||
@@ -1865,6 +1888,9 @@ static cli_config parse_options(int argc, char **argv) {
|
||||
} else if (!strcmp(arg, "--temp")) {
|
||||
c.gen.temperature = parse_float_range(need_arg(&i, argc, argv, arg), arg, 0.0f, 100.0f);
|
||||
c.gen.temperature_set = true;
|
||||
} else if (!strcmp(arg, "--top-k")) {
|
||||
c.gen.top_k = parse_nonnegative_int(need_arg(&i, argc, argv, arg), arg);
|
||||
c.gen.top_k_set = true;
|
||||
} else if (!strcmp(arg, "--top-p")) {
|
||||
c.gen.top_p = parse_float_range(need_arg(&i, argc, argv, arg), arg, 0.0f, 1.0f);
|
||||
c.gen.top_p_set = true;
|
||||
@@ -2118,6 +2144,9 @@ int main(int argc, char **argv) {
|
||||
return 1;
|
||||
}
|
||||
cli_apply_model_sampling_defaults(engine, &cfg.gen);
|
||||
if (!cfg.gen.system_set) {
|
||||
cfg.gen.system = ds4_engine_default_system_prompt(engine);
|
||||
}
|
||||
if (cfg.engine.tp.role == DS4_TP_WORKER) {
|
||||
int rc = ds4_tp_worker_run(engine, &cfg.engine.tp);
|
||||
ds4_engine_close(engine);
|
||||
|
||||
+1985
-35
File diff suppressed because it is too large.
Load diff
@@ -77,6 +77,10 @@ int ds4_gpu_pack_slot_rows_f32_tensor(
|
||||
int ds4_gpu_begin_commands(void);
|
||||
int ds4_gpu_flush_encoder(void);
|
||||
int ds4_gpu_flush_commands(void);
|
||||
/* Commit the active batch without waiting; later batches remain queue-ordered. */
|
||||
int ds4_gpu_submit_commands(void);
|
||||
int ds4_gpu_wait_submitted_commands(void);
|
||||
int ds4_gpu_discard_commands(void);
|
||||
int ds4_gpu_commands_active(void);
|
||||
int ds4_gpu_signal_selected_readback_ready(uint64_t *event_value);
|
||||
int ds4_gpu_commit_and_wait_selected_readback(uint64_t event_value, const char *label);
|
||||
@@ -137,6 +141,7 @@ int ds4_gpu_preload_q4_expert_tables(const void *model_map, uint64_t model_size,
|
||||
uint32_t n_total_expert);
|
||||
int ds4_gpu_should_use_managed_kv_cache(uint64_t kv_cache_bytes, uint64_t context_bytes);
|
||||
void ds4_gpu_set_quality(bool quality);
|
||||
void ds4_gpu_set_tensor_matmul_suppressed(bool suppressed);
|
||||
void ds4_gpu_set_glm_model(bool enabled);
|
||||
void ds4_gpu_set_ssd_streaming(bool enabled);
|
||||
void ds4_gpu_set_glm_streaming_prefill_full_layer(bool enabled);
|
||||
@@ -489,6 +494,14 @@ int ds4_gpu_argmax_tensor(
|
||||
const ds4_gpu_tensor *logits,
|
||||
uint32_t n_vocab);
|
||||
|
||||
#ifdef DS4_ROCM_BUILD
|
||||
int ds4_gpu_argmax_rows_tensor(
|
||||
ds4_gpu_tensor *out_idx,
|
||||
const ds4_gpu_tensor *logits,
|
||||
uint32_t n_vocab,
|
||||
uint32_t n_rows);
|
||||
#endif
|
||||
|
||||
int ds4_gpu_dsv4_topk_mask_tensor(
|
||||
ds4_gpu_tensor *mask,
|
||||
const ds4_gpu_tensor *topk,
|
||||
@@ -514,6 +527,16 @@ int ds4_gpu_matmul_q8_0_tensor(
|
||||
const ds4_gpu_tensor *x,
|
||||
uint64_t n_tok);
|
||||
|
||||
int ds4_gpu_matmul_q8_0_dflash_tensor(
|
||||
ds4_gpu_tensor *out,
|
||||
const void *model_map,
|
||||
uint64_t model_size,
|
||||
uint64_t weight_offset,
|
||||
uint64_t in_dim,
|
||||
uint64_t out_dim,
|
||||
const ds4_gpu_tensor *x,
|
||||
uint64_t n_tok);
|
||||
|
||||
int ds4_gpu_matmul_q8_0_decode_mpp_tensor(
|
||||
ds4_gpu_tensor *out,
|
||||
const void *model_map,
|
||||
@@ -577,6 +600,18 @@ int ds4_gpu_matmul_quant_rows_scalar_tensor(
|
||||
const ds4_gpu_tensor *x,
|
||||
uint64_t n_tok);
|
||||
|
||||
/* The original Laguna Q4_K_M recipe keeps selected down projections and the
|
||||
* output head in Q6_K. */
|
||||
int ds4_gpu_matmul_q6_K_tensor(
|
||||
ds4_gpu_tensor *out,
|
||||
const void *model_map,
|
||||
uint64_t model_size,
|
||||
uint64_t weight_offset,
|
||||
uint64_t in_dim,
|
||||
uint64_t out_dim,
|
||||
const ds4_gpu_tensor *x,
|
||||
uint64_t n_tok);
|
||||
|
||||
/* Optional fused GPU operations.
|
||||
*
|
||||
* These are acceleration hooks, not required backend primitives. A backend
|
||||
@@ -621,6 +656,18 @@ int ds4_gpu_matmul_q8_0_pair_decode_rows_exact_tensor(
|
||||
const ds4_gpu_tensor *x,
|
||||
uint32_t n_rows);
|
||||
|
||||
/* Implemented by the Metal and ROCm backends; the DFlash verifier is the only
|
||||
* caller, and it needs each row bit-identical to single-row decode. */
|
||||
int ds4_gpu_matmul_f32_decode_rows_exact_tensor(
|
||||
ds4_gpu_tensor *out,
|
||||
const void *model_map,
|
||||
uint64_t model_size,
|
||||
uint64_t weight_offset,
|
||||
uint64_t in_dim,
|
||||
uint64_t out_dim,
|
||||
const ds4_gpu_tensor *x,
|
||||
uint32_t n_rows);
|
||||
|
||||
int ds4_gpu_matmul_q8_0_f16_out_tensor(
|
||||
ds4_gpu_tensor *out_h,
|
||||
const void *model_map,
|
||||
@@ -957,6 +1004,148 @@ int ds4_gpu_glm_rope_tail_tensor(
|
||||
float beta_fast,
|
||||
float beta_slow);
|
||||
|
||||
int ds4_gpu_laguna_head_rms_norm_rope_tensor(
|
||||
ds4_gpu_tensor *x,
|
||||
const void *model_map,
|
||||
uint64_t model_size,
|
||||
uint64_t weight_offset,
|
||||
uint32_t n_tokens,
|
||||
uint32_t n_head,
|
||||
uint32_t head_dim,
|
||||
uint32_t n_rot,
|
||||
uint32_t pos0,
|
||||
uint32_t n_ctx_orig,
|
||||
float freq_base,
|
||||
float freq_scale,
|
||||
float ext_factor,
|
||||
float attn_factor,
|
||||
float beta_fast,
|
||||
float beta_slow,
|
||||
float eps);
|
||||
|
||||
int ds4_gpu_laguna_qk_head_rms_norm_rope_tensor(
|
||||
ds4_gpu_tensor *q,
|
||||
ds4_gpu_tensor *k,
|
||||
const void *model_map,
|
||||
uint64_t model_size,
|
||||
uint64_t q_weight_offset,
|
||||
uint64_t k_weight_offset,
|
||||
uint32_t n_tokens,
|
||||
uint32_t n_q_head,
|
||||
uint32_t n_k_head,
|
||||
uint32_t head_dim,
|
||||
uint32_t n_rot,
|
||||
uint32_t pos0,
|
||||
uint32_t n_ctx_orig,
|
||||
float freq_base,
|
||||
float freq_scale,
|
||||
float ext_factor,
|
||||
float attn_factor,
|
||||
float beta_fast,
|
||||
float beta_slow,
|
||||
float eps);
|
||||
|
||||
int ds4_gpu_laguna_qkvg_f16_tensor(
|
||||
ds4_gpu_tensor *q,
|
||||
ds4_gpu_tensor *k,
|
||||
ds4_gpu_tensor *v,
|
||||
ds4_gpu_tensor *gate,
|
||||
const void *model_map,
|
||||
uint64_t model_size,
|
||||
uint64_t q_weight_offset,
|
||||
uint64_t k_weight_offset,
|
||||
uint64_t v_weight_offset,
|
||||
uint64_t gate_weight_offset,
|
||||
uint32_t in_dim,
|
||||
uint32_t q_dim,
|
||||
uint32_t kv_dim,
|
||||
uint32_t gate_dim,
|
||||
const ds4_gpu_tensor *x);
|
||||
|
||||
int ds4_gpu_laguna_attn_output_residual_f16_tensor(
|
||||
ds4_gpu_tensor *out,
|
||||
const void *model_map,
|
||||
uint64_t model_size,
|
||||
uint64_t weight_offset,
|
||||
uint32_t in_dim,
|
||||
uint32_t out_dim,
|
||||
const ds4_gpu_tensor *x,
|
||||
const ds4_gpu_tensor *residual);
|
||||
|
||||
int ds4_gpu_laguna_store_attention_tensor(
|
||||
ds4_gpu_tensor *heads,
|
||||
ds4_gpu_tensor *key_cache,
|
||||
ds4_gpu_tensor *value_cache,
|
||||
const ds4_gpu_tensor *q,
|
||||
const ds4_gpu_tensor *k,
|
||||
const ds4_gpu_tensor *v,
|
||||
const ds4_gpu_tensor *gate,
|
||||
uint32_t pos,
|
||||
uint32_t cache_cap,
|
||||
uint32_t key_start,
|
||||
uint32_t key_count,
|
||||
uint32_t n_head,
|
||||
uint32_t n_head_kv,
|
||||
uint32_t head_dim,
|
||||
float scale);
|
||||
|
||||
int ds4_gpu_laguna_attention_prefill_tensor(
|
||||
ds4_gpu_tensor *heads,
|
||||
ds4_gpu_tensor *key_cache,
|
||||
ds4_gpu_tensor *value_cache,
|
||||
ds4_gpu_tensor *staged_key,
|
||||
ds4_gpu_tensor *staged_value,
|
||||
const ds4_gpu_tensor *q,
|
||||
const ds4_gpu_tensor *k,
|
||||
const ds4_gpu_tensor *v,
|
||||
const ds4_gpu_tensor *gate,
|
||||
uint32_t pos0,
|
||||
uint32_t n_tokens,
|
||||
uint32_t cache_cap,
|
||||
uint32_t n_head,
|
||||
uint32_t n_head_kv,
|
||||
uint32_t head_dim,
|
||||
float scale,
|
||||
int split_decode_rows);
|
||||
|
||||
int ds4_gpu_dflash_capture_rows_tensor(
|
||||
ds4_gpu_tensor *features,
|
||||
const ds4_gpu_tensor *src,
|
||||
uint32_t src_row0,
|
||||
uint32_t dst_row0,
|
||||
uint32_t n_rows,
|
||||
uint32_t n_embd,
|
||||
uint32_t n_aux,
|
||||
uint32_t aux_index);
|
||||
|
||||
int ds4_gpu_dflash_aux_norm_tensor(
|
||||
ds4_gpu_tensor *features,
|
||||
const void *model_map,
|
||||
uint64_t model_size,
|
||||
uint64_t weight_offset,
|
||||
uint32_t n_rows,
|
||||
uint32_t n_embd,
|
||||
uint32_t n_aux,
|
||||
float eps);
|
||||
|
||||
int ds4_gpu_dflash_commit_kv_tensor(
|
||||
ds4_gpu_tensor *key_cache,
|
||||
ds4_gpu_tensor *value_cache,
|
||||
const ds4_gpu_tensor *k,
|
||||
const ds4_gpu_tensor *v,
|
||||
uint32_t pos0,
|
||||
uint32_t n_rows,
|
||||
uint32_t cache_cap,
|
||||
uint32_t n_head_kv,
|
||||
uint32_t head_dim);
|
||||
|
||||
int ds4_gpu_dflash_probabilities_tensor(
|
||||
ds4_gpu_tensor *probabilities,
|
||||
const ds4_gpu_tensor *logits,
|
||||
const ds4_gpu_tensor *argmax,
|
||||
uint32_t n_rows,
|
||||
uint32_t n_vocab);
|
||||
|
||||
int ds4_gpu_glm_kv_lora_rms_norm_tensor(
|
||||
ds4_gpu_tensor *out,
|
||||
const ds4_gpu_tensor *kv_raw,
|
||||
@@ -2138,6 +2327,43 @@ int ds4_gpu_glm_routed_moe_one_tensor(
|
||||
const ds4_gpu_tensor *x,
|
||||
bool force_resident);
|
||||
|
||||
typedef struct {
|
||||
uint64_t gate_offset;
|
||||
uint64_t up_offset;
|
||||
uint64_t down_offset;
|
||||
uint32_t gate_type;
|
||||
uint32_t up_type;
|
||||
uint32_t down_type;
|
||||
uint64_t gate_expert_bytes;
|
||||
uint64_t gate_row_bytes;
|
||||
uint64_t up_expert_bytes;
|
||||
uint64_t up_row_bytes;
|
||||
uint64_t down_expert_bytes;
|
||||
uint64_t down_row_bytes;
|
||||
} ds4_gpu_laguna_moe_desc;
|
||||
|
||||
/* Decode-only Laguna path. Routed and shared experts use independent thread
|
||||
* groups in two common dispatches, preserving each projection's arithmetic. */
|
||||
int ds4_gpu_laguna_routed_shared_moe_one_tensor(
|
||||
ds4_gpu_tensor *routed_out,
|
||||
ds4_gpu_tensor *routed_mid,
|
||||
ds4_gpu_tensor *shared_out,
|
||||
ds4_gpu_tensor *shared_mid,
|
||||
const void *model_map,
|
||||
uint64_t model_size,
|
||||
const ds4_gpu_laguna_moe_desc *routed,
|
||||
const ds4_gpu_laguna_moe_desc *shared,
|
||||
uint32_t expert_in_dim,
|
||||
uint32_t expert_mid_dim,
|
||||
uint32_t out_dim,
|
||||
const ds4_gpu_tensor *selected,
|
||||
const ds4_gpu_tensor *weights,
|
||||
uint32_t n_total_expert,
|
||||
uint32_t n_expert,
|
||||
const ds4_gpu_tensor *shared_selected,
|
||||
const ds4_gpu_tensor *shared_weight,
|
||||
const ds4_gpu_tensor *x);
|
||||
|
||||
int ds4_gpu_glm_routed_moe_batch_tensor(
|
||||
ds4_gpu_tensor *out,
|
||||
ds4_gpu_tensor *mid,
|
||||
@@ -2197,6 +2423,71 @@ int ds4_gpu_glm_routed_moe_batch_direct_scalar_q4_tensor(
|
||||
uint32_t n_tokens,
|
||||
uint32_t mid_token_stride);
|
||||
|
||||
#if defined(__APPLE__) || defined(DS4_ROCM_BUILD) || \
|
||||
defined(DS4_NATIVE_CUDA_BUILD)
|
||||
/* DFlash verifier path: batch Q2_K/Q3_K rows while retaining decode math. */
|
||||
int ds4_gpu_glm_routed_moe_batch_decode_exact_q2_q3_tensor(
|
||||
ds4_gpu_tensor *out,
|
||||
ds4_gpu_tensor *mid,
|
||||
const void *model_map,
|
||||
uint64_t model_size,
|
||||
uint64_t gate_offset,
|
||||
uint64_t up_offset,
|
||||
uint64_t down_offset,
|
||||
uint32_t gate_type,
|
||||
uint32_t up_type,
|
||||
uint32_t down_type,
|
||||
uint64_t gate_expert_bytes,
|
||||
uint64_t gate_row_bytes,
|
||||
uint64_t up_expert_bytes,
|
||||
uint64_t up_row_bytes,
|
||||
uint64_t down_expert_bytes,
|
||||
uint64_t down_row_bytes,
|
||||
uint32_t expert_in_dim,
|
||||
uint32_t expert_mid_dim,
|
||||
uint32_t out_dim,
|
||||
const ds4_gpu_tensor *selected,
|
||||
const ds4_gpu_tensor *weights,
|
||||
uint32_t n_total_expert,
|
||||
uint32_t n_expert,
|
||||
uint32_t layer_index,
|
||||
const ds4_gpu_tensor *x,
|
||||
uint32_t n_tokens,
|
||||
uint32_t mid_token_stride);
|
||||
#endif
|
||||
|
||||
#if defined(__APPLE__)
|
||||
/* Metal verifier path: batch rows while preserving decode's Q4 kernels. */
|
||||
int ds4_gpu_glm_routed_moe_batch_decode_exact_q4_tensor(
|
||||
ds4_gpu_tensor *out,
|
||||
ds4_gpu_tensor *mid,
|
||||
const void *model_map,
|
||||
uint64_t model_size,
|
||||
uint64_t gate_offset,
|
||||
uint64_t up_offset,
|
||||
uint64_t down_offset,
|
||||
uint32_t gate_type,
|
||||
uint32_t up_type,
|
||||
uint32_t down_type,
|
||||
uint64_t gate_expert_bytes,
|
||||
uint64_t gate_row_bytes,
|
||||
uint64_t up_expert_bytes,
|
||||
uint64_t up_row_bytes,
|
||||
uint64_t down_expert_bytes,
|
||||
uint64_t down_row_bytes,
|
||||
uint32_t expert_in_dim,
|
||||
uint32_t expert_mid_dim,
|
||||
uint32_t out_dim,
|
||||
const ds4_gpu_tensor *selected,
|
||||
const ds4_gpu_tensor *weights,
|
||||
uint32_t n_total_expert,
|
||||
uint32_t n_expert,
|
||||
uint32_t layer_index,
|
||||
const ds4_gpu_tensor *x,
|
||||
uint32_t n_tokens,
|
||||
uint32_t mid_token_stride);
|
||||
#endif
|
||||
|
||||
int ds4_gpu_routed_moe_set_selected_override(const int32_t *selected, uint32_t n_selected);
|
||||
void ds4_gpu_set_glm_mtp_verify_mode(bool enabled);
|
||||
|
||||
|
||||
+8
-4
@@ -173,12 +173,15 @@ static void print_model_runtime(FILE *fp, const help_colors *c,
|
||||
opt(fp, c, "--ssd-streaming-full-layers N", "GLM Metal streaming: keep the first N routed layers fully resident. Default: auto from NGB expert budget; use 0 to disable.");
|
||||
opt(fp, c, "--ssd-streaming-preload-experts N", "SSD streaming: upfront popularity preload count. DeepSeek auto-seeds by default; GLM demand-fills unless N is explicit.");
|
||||
opt(fp, c, "--simulate-used-memory NGB", "Diagnostic: lock N GiB before model load to simulate a smaller-memory machine.");
|
||||
opt(fp, c, "--prefill-chunk N", "Graph prefill chunk size. Default: CUDA TP 2048; PRO long prompts 8192; others 4096.");
|
||||
opt(fp, c, "--prefill-chunk N", "Graph prefill chunk size. Default: CUDA TP 2048; PRO 8192; Laguna 16384; others 4096.");
|
||||
if (full) {
|
||||
if (tool != DS4_HELP_BENCH) {
|
||||
opt(fp, c, "--mtp FILE", "Optional MTP support GGUF used for draft-token probes.");
|
||||
}
|
||||
if (tool == DS4_HELP_DS4 || tool == DS4_HELP_AGENT || tool == DS4_HELP_SERVER) {
|
||||
opt(fp, c, "--dflash FILE", "Laguna DFlash support GGUF for greedy speculative decoding.");
|
||||
opt(fp, c, "--dflash-draft N", "Maximum adaptive DFlash draft positions, 1..15. CUDA default: 15; other backends: 3");
|
||||
opt(fp, c, "--dflash-p-min P", "Stop before proposals below probability P, 0..1. Default: 0.4; 0 keeps fixed verifier width");
|
||||
opt(fp, c, "--mtp-draft N", "Maximum autoregressive MTP draft tokens. Default: 1");
|
||||
opt(fp, c, "--mtp-margin F", "Verifier confidence margin for fast MTP acceptance. Default: 3");
|
||||
opt(fp, c, "--glm-mtp", "Enable integrated greedy GLM MTP speculation.");
|
||||
@@ -200,10 +203,11 @@ static void print_sampling(FILE *fp, const help_colors *c, bool full) {
|
||||
title(fp, c, "Prompt And Sampling");
|
||||
opt(fp, c, "-n, --tokens N", "Maximum generated tokens.");
|
||||
opt(fp, c, "--temp F", "Sampling temperature. 0 is greedy/deterministic.");
|
||||
opt(fp, c, "--top-k N", "Sample only from the N highest-scoring tokens. 0 disables.");
|
||||
opt(fp, c, "--top-p F", "Nucleus sampling probability.");
|
||||
opt(fp, c, "--min-p F", "Keep tokens scoring at least F times the top token.");
|
||||
opt(fp, c, "--seed N", "Sampling seed for reproducible non-greedy runs.");
|
||||
para(fp, c, "GLM CLI and agent runs default to temperature 1.0, top-p 0.95, and min-p 0 unless those options are set explicitly.");
|
||||
para(fp, c, "GLM defaults to temperature 1.0, top-p 0.95, and min-p 0. Laguna defaults to temperature 0.7, top-k 20, top-p 0.95, and min-p 0.05. Explicit options always win.");
|
||||
opt(fp, c, "--think", "Use normal thinking mode.");
|
||||
opt(fp, c, "--think-max", "Use Think Max when context is large enough.");
|
||||
opt(fp, c, "--nothink", "Disable thinking and ask for direct replies.");
|
||||
@@ -333,7 +337,7 @@ static void print_server_api(FILE *fp, const help_colors *c) {
|
||||
opt(fp, c, "--trace FILE", "Write prompts, cache decisions, output, and tool calls.");
|
||||
opt(fp, c, "--batched-session N", "Keep N resident sessions and batch decode-ready requests.");
|
||||
para(fp, c, "Endpoints: /v1/chat/completions, /v1/responses, /v1/completions, and /v1/messages.");
|
||||
para(fp, c, "Model endpoint aliases include deepseek-v4-flash and deepseek-v4-pro; both serve the loaded GGUF.");
|
||||
para(fp, c, "Model aliases are available for DeepSeek V4, GLM-5.2, and Laguna-S-2.1; every alias serves the loaded GGUF.");
|
||||
fputc('\n', fp);
|
||||
}
|
||||
|
||||
@@ -540,7 +544,7 @@ static void print_topic(FILE *fp, const help_colors *c, ds4_help_tool tool, cons
|
||||
else if (tool == DS4_HELP_AGENT && streq(topic, "tools")) {
|
||||
title(fp, c, "Agent Tool System");
|
||||
para(fp, c, "The agent can read, search, write, edit, run bash, and browse through Chrome-backed web tools.");
|
||||
para(fp, c, "DeepSeek-family models emit DSML tool calls; GLM models use native <tool_call> syntax. Both are rendered live in the terminal.");
|
||||
para(fp, c, "DeepSeek-family models emit DSML tool calls; GLM and Laguna models use native <tool_call> syntax. Both are rendered live in the terminal.");
|
||||
para(fp, c, "Edit uses exact old/new replacement; [upto] can bridge a unique head and tail for large anchored edits.");
|
||||
fputc('\n', fp);
|
||||
} else if (tool == DS4_HELP_BENCH && streq(topic, "benchmark")) print_bench_specific(fp, c);
|
||||
|
||||
+2775
-17
File diff suppressed because it is too large.
Load diff
+13
@@ -69,6 +69,13 @@ typedef struct {
|
||||
uint16_t dmin;
|
||||
} cuda_block_q2_K;
|
||||
|
||||
typedef struct {
|
||||
uint8_t hmask[CUDA_QK_K / 8];
|
||||
uint8_t qs[CUDA_QK_K / 4];
|
||||
uint8_t scales[12];
|
||||
uint16_t d;
|
||||
} cuda_block_q3_K;
|
||||
|
||||
typedef struct {
|
||||
uint16_t d;
|
||||
uint16_t dmin;
|
||||
@@ -87,6 +94,8 @@ typedef struct {
|
||||
uint16_t qs[CUDA_QK_K / 8];
|
||||
} cuda_block_iq2_xxs;
|
||||
|
||||
static_assert(sizeof(cuda_block_q3_K) == 110, "Q3_K block layout mismatch");
|
||||
|
||||
#include "ds4_iq2_tables_cuda.inc"
|
||||
|
||||
#include "rocm/ds4_rocm_runtime.cuh"
|
||||
@@ -97,6 +106,10 @@ typedef struct {
|
||||
|
||||
#include "rocm/ds4_rocm_norm_rope.cuh"
|
||||
|
||||
#include "rocm/ds4_rocm_laguna.cuh"
|
||||
|
||||
#include "rocm/ds4_rocm_dflash.cuh"
|
||||
|
||||
#include "rocm/ds4_rocm_fp8_kv.cuh"
|
||||
|
||||
#include "rocm/ds4_rocm_attention.cuh"
|
||||
|
||||
+51
-18
@@ -207,24 +207,6 @@ extern "C" int ds4_gpu_set_decode_score_vec4(int enabled) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
extern "C" int ds4_gpu_matmul_q8_0_decode_rows_exact_tensor(
|
||||
ds4_gpu_tensor *out, const void *model_map, uint64_t model_size,
|
||||
uint64_t weight_offset, uint64_t in_dim, uint64_t out_dim,
|
||||
const ds4_gpu_tensor *x, uint32_t n_rows) {
|
||||
return ds4_gpu_matmul_q8_0_tensor(out, model_map, model_size,
|
||||
weight_offset, in_dim, out_dim, x,
|
||||
n_rows);
|
||||
}
|
||||
|
||||
extern "C" int ds4_gpu_matmul_q8_0_pair_decode_rows_exact_tensor(
|
||||
ds4_gpu_tensor *out0, ds4_gpu_tensor *out1, const void *model_map,
|
||||
uint64_t model_size, uint64_t weight0_offset,
|
||||
uint64_t weight1_offset, uint64_t in_dim, uint64_t out0_dim,
|
||||
uint64_t out1_dim, const ds4_gpu_tensor *x, uint32_t n_rows) {
|
||||
return ds4_gpu_matmul_q8_0_pair_tensor(
|
||||
out0, out1, model_map, model_size, weight0_offset, weight1_offset,
|
||||
in_dim, out0_dim, out1_dim, x, n_rows);
|
||||
}
|
||||
|
||||
extern "C" int ds4_gpu_matmul_f16_router_rows_exact_tensor(
|
||||
ds4_gpu_tensor *out, const void *model_map, uint64_t model_size,
|
||||
@@ -388,3 +370,54 @@ extern "C" int ds4_gpu_glm_attention_indexed_batch_typed_tensor(
|
||||
value_dim, n_ctx_orig, freq_base, freq_scale, ext_factor,
|
||||
attn_factor, beta_fast, beta_slow);
|
||||
}
|
||||
|
||||
extern "C" int ds4_gpu_matmul_q6_K_tensor(
|
||||
ds4_gpu_tensor *out, const void *model_map, uint64_t model_size,
|
||||
uint64_t weight_offset, uint64_t in_dim, uint64_t out_dim,
|
||||
const ds4_gpu_tensor *x, uint64_t n_tok) {
|
||||
(void)out; (void)model_map; (void)model_size; (void)weight_offset;
|
||||
(void)in_dim; (void)out_dim; (void)x; (void)n_tok;
|
||||
return 0;
|
||||
}
|
||||
|
||||
extern "C" int ds4_gpu_laguna_routed_shared_moe_one_tensor(
|
||||
ds4_gpu_tensor *routed_out, ds4_gpu_tensor *routed_mid,
|
||||
ds4_gpu_tensor *shared_out, ds4_gpu_tensor *shared_mid,
|
||||
const void *model_map, uint64_t model_size,
|
||||
const ds4_gpu_laguna_moe_desc *routed,
|
||||
const ds4_gpu_laguna_moe_desc *shared,
|
||||
uint32_t expert_in_dim, uint32_t expert_mid_dim, uint32_t out_dim,
|
||||
const ds4_gpu_tensor *selected, const ds4_gpu_tensor *weights,
|
||||
uint32_t n_total_expert, uint32_t n_expert,
|
||||
const ds4_gpu_tensor *shared_selected,
|
||||
const ds4_gpu_tensor *shared_weight, const ds4_gpu_tensor *x) {
|
||||
(void)routed_out; (void)routed_mid; (void)shared_out; (void)shared_mid;
|
||||
(void)model_map; (void)model_size; (void)routed; (void)shared;
|
||||
(void)expert_in_dim; (void)expert_mid_dim; (void)out_dim;
|
||||
(void)selected; (void)weights; (void)n_total_expert; (void)n_expert;
|
||||
(void)shared_selected; (void)shared_weight; (void)x;
|
||||
return 0;
|
||||
}
|
||||
|
||||
extern "C" int ds4_gpu_laguna_qkvg_f16_tensor(
|
||||
ds4_gpu_tensor *q, ds4_gpu_tensor *k, ds4_gpu_tensor *v,
|
||||
ds4_gpu_tensor *gate, const void *model_map, uint64_t model_size,
|
||||
uint64_t q_weight_offset, uint64_t k_weight_offset,
|
||||
uint64_t v_weight_offset, uint64_t gate_weight_offset,
|
||||
uint32_t in_dim, uint32_t q_dim, uint32_t kv_dim,
|
||||
uint32_t gate_dim, const ds4_gpu_tensor *x) {
|
||||
(void)q; (void)k; (void)v; (void)gate; (void)model_map; (void)model_size;
|
||||
(void)q_weight_offset; (void)k_weight_offset; (void)v_weight_offset;
|
||||
(void)gate_weight_offset; (void)in_dim; (void)q_dim; (void)kv_dim;
|
||||
(void)gate_dim; (void)x;
|
||||
return 0;
|
||||
}
|
||||
|
||||
extern "C" int ds4_gpu_laguna_attn_output_residual_f16_tensor(
|
||||
ds4_gpu_tensor *out, const void *model_map, uint64_t model_size,
|
||||
uint64_t weight_offset, uint32_t in_dim, uint32_t out_dim,
|
||||
const ds4_gpu_tensor *x, const ds4_gpu_tensor *residual) {
|
||||
(void)out; (void)model_map; (void)model_size; (void)weight_offset;
|
||||
(void)in_dim; (void)out_dim; (void)x; (void)residual;
|
||||
return 0;
|
||||
}
|
||||
+396
-10
@@ -505,6 +505,7 @@ typedef enum {
|
||||
typedef enum {
|
||||
SERVER_MODEL_SYNTAX_DEEPSEEK,
|
||||
SERVER_MODEL_SYNTAX_GLM,
|
||||
SERVER_MODEL_SYNTAX_LAGUNA,
|
||||
} server_model_syntax;
|
||||
|
||||
static void random_tool_id(char *dst, size_t dstlen, api_style api) {
|
||||
@@ -789,6 +790,16 @@ static void request_init(request *r, req_kind kind, int max_tokens) {
|
||||
r->think_mode = DS4_THINK_HIGH;
|
||||
}
|
||||
|
||||
static void request_apply_model_sampling_defaults(ds4_engine *engine, request *r) {
|
||||
float temperature, top_p, min_p;
|
||||
int top_k;
|
||||
ds4_engine_sampling_defaults(engine, &temperature, &top_k, &top_p, &min_p);
|
||||
if (!r->temperature_set) r->temperature = temperature;
|
||||
if (!r->top_k_set) r->top_k = top_k;
|
||||
if (!r->top_p_set) r->top_p = top_p;
|
||||
if (!r->min_p_set) r->min_p = min_p;
|
||||
}
|
||||
|
||||
static void request_free(request *r) {
|
||||
ds4_tokens_free(&r->prompt);
|
||||
free(r->model);
|
||||
@@ -920,6 +931,9 @@ static bool parse_output_config_effort(const char **p, ds4_think_mode *effort) {
|
||||
static bool model_alias_disables_thinking(const char *model) {
|
||||
return model &&
|
||||
(!strcmp(model, "deepseek-chat") ||
|
||||
!strcmp(model, "laguna-s-2.1-chat") ||
|
||||
!strcmp(model, "laguna-s-2.1-no-think") ||
|
||||
!strcmp(model, "laguna-s-2.1-nothink") ||
|
||||
!strcmp(model, "glm-5.2-chat") ||
|
||||
!strcmp(model, "glm-5.2-no-think") ||
|
||||
!strcmp(model, "glm-5.2-nothink") ||
|
||||
@@ -929,17 +943,20 @@ static bool model_alias_disables_thinking(const char *model) {
|
||||
static bool model_alias_enables_thinking(const char *model) {
|
||||
return model &&
|
||||
(!strcmp(model, "deepseek-reasoner") ||
|
||||
!strcmp(model, "laguna-s-2.1-reasoner") ||
|
||||
!strcmp(model, "glm-5.2-reasoner") ||
|
||||
!strcmp(model, "zai/glm-5.2-reasoner"));
|
||||
}
|
||||
|
||||
static server_model_syntax server_model_syntax_for_engine(ds4_engine *engine) {
|
||||
return ds4_engine_is_glm_dsa(engine) ?
|
||||
SERVER_MODEL_SYNTAX_GLM : SERVER_MODEL_SYNTAX_DEEPSEEK;
|
||||
if (ds4_engine_is_glm_dsa(engine)) return SERVER_MODEL_SYNTAX_GLM;
|
||||
if (ds4_engine_is_laguna(engine)) return SERVER_MODEL_SYNTAX_LAGUNA;
|
||||
return SERVER_MODEL_SYNTAX_DEEPSEEK;
|
||||
}
|
||||
|
||||
static const char *server_model_id_from_engine(ds4_engine *engine) {
|
||||
if (ds4_engine_is_glm_dsa(engine)) return "glm-5.2";
|
||||
if (ds4_engine_is_laguna(engine)) return "laguna-s-2.1";
|
||||
return ds4_engine_model_id(engine) == 1 ?
|
||||
"deepseek-v4-pro" : "deepseek-v4-flash";
|
||||
}
|
||||
@@ -948,6 +965,12 @@ static bool server_model_alias_known(const char *id) {
|
||||
return id &&
|
||||
(!strcmp(id, "deepseek-v4-flash") ||
|
||||
!strcmp(id, "deepseek-v4-pro") ||
|
||||
!strcmp(id, "laguna-s-2.1") ||
|
||||
!strcmp(id, "laguna-s-2.1-chat") ||
|
||||
!strcmp(id, "laguna-s-2.1-no-think") ||
|
||||
!strcmp(id, "laguna-s-2.1-nothink") ||
|
||||
!strcmp(id, "laguna-s-2.1-reasoner") ||
|
||||
!strcmp(id, "poolside/laguna-s-2.1") ||
|
||||
!strcmp(id, "glm-5.2") ||
|
||||
!strcmp(id, "glm-5.2-chat") ||
|
||||
!strcmp(id, "glm-5.2-no-think") ||
|
||||
@@ -2394,12 +2417,36 @@ static void append_glm_tool_calls_text(buf *b, const tool_calls *calls,
|
||||
}
|
||||
}
|
||||
|
||||
static void append_laguna_tool_calls_text(buf *b, const tool_calls *calls,
|
||||
const tool_schema_orders *tool_orders) {
|
||||
if (!calls || calls->len == 0) return;
|
||||
if (calls->raw_tool_text && calls->raw_tool_text[0]) {
|
||||
buf_puts(b, calls->raw_tool_text);
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < calls->len; i++) {
|
||||
const tool_call *tc = &calls->v[i];
|
||||
const tool_schema_order *order =
|
||||
tool_schema_orders_find(tool_orders, tc->name);
|
||||
buf_puts(b, "<tool_call>");
|
||||
buf_puts(b, tc->name ? tc->name : "");
|
||||
if (!append_glm_arguments_from_json(b, tc->arguments, order)) {
|
||||
buf_puts(b, "<arg_key>arguments</arg_key><arg_value>");
|
||||
append_glm_arg_value_text(b, tc->arguments);
|
||||
buf_puts(b, "</arg_value>");
|
||||
}
|
||||
buf_puts(b, "</tool_call>");
|
||||
}
|
||||
}
|
||||
|
||||
static void append_tool_calls_text_for_syntax(buf *b,
|
||||
server_model_syntax syntax,
|
||||
const tool_calls *calls,
|
||||
const tool_schema_orders *tool_orders) {
|
||||
if (syntax == SERVER_MODEL_SYNTAX_GLM) {
|
||||
append_glm_tool_calls_text(b, calls, tool_orders);
|
||||
} else if (syntax == SERVER_MODEL_SYNTAX_LAGUNA) {
|
||||
append_laguna_tool_calls_text(b, calls, tool_orders);
|
||||
} else {
|
||||
append_dsml_tool_calls_text(b, calls);
|
||||
}
|
||||
@@ -2594,6 +2641,96 @@ static char *render_glm_chat_prompt_text(const chat_msgs *msgs,
|
||||
return buf_take(&out);
|
||||
}
|
||||
|
||||
static bool text_has_nonspace(const char *s) {
|
||||
for (s = s ? s : ""; *s; s++) {
|
||||
if (!isspace((unsigned char)*s)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void buf_puts_rstrip(buf *b, const char *s) {
|
||||
const char *start = s ? s : "";
|
||||
const char *end = start + strlen(start);
|
||||
while (end > start && isspace((unsigned char)end[-1])) end--;
|
||||
buf_append(b, start, (size_t)(end - start));
|
||||
}
|
||||
|
||||
static char *render_laguna_chat_prompt_text(const chat_msgs *msgs,
|
||||
const char *tool_schemas,
|
||||
const tool_schema_orders *tool_orders,
|
||||
ds4_think_mode think_mode) {
|
||||
const bool think = ds4_think_mode_enabled(think_mode);
|
||||
const bool tools = tool_schemas && tool_schemas[0];
|
||||
const char *system =
|
||||
"You are a helpful, conversationally-fluent assistant made by Poolside. "
|
||||
"You are here to be helpful to users through natural language conversations.";
|
||||
int start = 0;
|
||||
if (msgs && msgs->len > 0 && role_is_system(msgs->v[0].role)) {
|
||||
system = msgs->v[0].content ? msgs->v[0].content : "";
|
||||
start = 1;
|
||||
}
|
||||
|
||||
buf out = {0};
|
||||
buf_puts(&out, "〈|EOS|〉");
|
||||
if (text_has_nonspace(system) || tools || think) {
|
||||
buf_puts(&out, "<system>");
|
||||
if (text_has_nonspace(system)) {
|
||||
buf_puts_rstrip(&out, system);
|
||||
if (tools) buf_puts(&out, "\n\n");
|
||||
}
|
||||
if (tools) {
|
||||
buf_puts(&out,
|
||||
"### Tools\n\n"
|
||||
"You may call functions to assist with the user query.\n"
|
||||
"All available function signatures are listed below:\n"
|
||||
"<available_tools>\n");
|
||||
buf_puts(&out, tool_schemas);
|
||||
if (out.len && out.ptr[out.len - 1] != '\n') buf_putc(&out, '\n');
|
||||
buf_puts(&out, "</available_tools>");
|
||||
}
|
||||
buf_puts(&out, "</system>\n");
|
||||
}
|
||||
|
||||
bool pending_assistant = false;
|
||||
for (int i = start; msgs && i < msgs->len; i++) {
|
||||
const chat_msg *m = &msgs->v[i];
|
||||
if (role_is_system(m->role)) {
|
||||
buf_puts(&out, "<system>");
|
||||
buf_puts(&out, m->content ? m->content : "");
|
||||
buf_puts(&out, "</system>\n");
|
||||
} else if (!strcmp(m->role, "user")) {
|
||||
buf_puts(&out, "<user>");
|
||||
buf_puts(&out, m->content ? m->content : "");
|
||||
buf_puts(&out, "</user>\n");
|
||||
pending_assistant = true;
|
||||
} else if (!strcmp(m->role, "tool") || !strcmp(m->role, "function")) {
|
||||
buf_puts(&out, "<tool_response>");
|
||||
append_glm_tool_response_text(&out, m->content);
|
||||
buf_puts(&out, "</tool_response>\n");
|
||||
pending_assistant = true;
|
||||
} else if (!strcmp(m->role, "assistant")) {
|
||||
buf_puts(&out, "<assistant>");
|
||||
if (think) {
|
||||
buf_puts(&out, "<think>");
|
||||
buf_puts(&out, m->reasoning ? m->reasoning : "");
|
||||
buf_puts(&out, "</think>");
|
||||
} else {
|
||||
buf_puts(&out, "</think>");
|
||||
}
|
||||
buf_puts(&out, m->content ? m->content : "");
|
||||
append_laguna_tool_calls_text(&out, &m->calls, tool_orders);
|
||||
buf_puts(&out, "</assistant>\n");
|
||||
pending_assistant = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (pending_assistant) {
|
||||
buf_puts(&out, "<assistant>");
|
||||
buf_puts(&out, think ? "<think>" : "</think>");
|
||||
}
|
||||
return buf_take(&out);
|
||||
}
|
||||
|
||||
static char *render_chat_prompt_text_for_syntax(server_model_syntax syntax,
|
||||
const chat_msgs *msgs,
|
||||
const char *tool_schemas,
|
||||
@@ -2603,6 +2740,10 @@ static char *render_chat_prompt_text_for_syntax(server_model_syntax syntax,
|
||||
return render_glm_chat_prompt_text(msgs, tool_schemas,
|
||||
tool_orders, think_mode);
|
||||
}
|
||||
if (syntax == SERVER_MODEL_SYNTAX_LAGUNA) {
|
||||
return render_laguna_chat_prompt_text(msgs, tool_schemas,
|
||||
tool_orders, think_mode);
|
||||
}
|
||||
return render_deepseek_chat_prompt_text(msgs, tool_schemas,
|
||||
tool_orders, think_mode);
|
||||
}
|
||||
@@ -2719,6 +2860,52 @@ static char *render_glm_live_tool_tail(const chat_msgs *msgs, int start,
|
||||
return buf_take(&out);
|
||||
}
|
||||
|
||||
static char *render_laguna_live_tool_tail(const chat_msgs *msgs, int start,
|
||||
const tool_schema_orders *tool_orders,
|
||||
ds4_think_mode think_mode) {
|
||||
const bool think = ds4_think_mode_enabled(think_mode);
|
||||
buf out = {0};
|
||||
/* The generated stop token is inspected but not accepted into the live KV. */
|
||||
buf_puts(&out, "</assistant>\n");
|
||||
bool pending_assistant = false;
|
||||
for (int i = start; msgs && i < msgs->len; i++) {
|
||||
const chat_msg *m = &msgs->v[i];
|
||||
if (role_is_system(m->role)) {
|
||||
buf_puts(&out, "<system>");
|
||||
buf_puts(&out, m->content ? m->content : "");
|
||||
buf_puts(&out, "</system>\n");
|
||||
} else if (!strcmp(m->role, "user")) {
|
||||
buf_puts(&out, "<user>");
|
||||
buf_puts(&out, m->content ? m->content : "");
|
||||
buf_puts(&out, "</user>\n");
|
||||
pending_assistant = true;
|
||||
} else if (!strcmp(m->role, "tool") || !strcmp(m->role, "function")) {
|
||||
buf_puts(&out, "<tool_response>");
|
||||
append_glm_tool_response_text(&out, m->content);
|
||||
buf_puts(&out, "</tool_response>\n");
|
||||
pending_assistant = true;
|
||||
} else if (!strcmp(m->role, "assistant")) {
|
||||
buf_puts(&out, "<assistant>");
|
||||
if (think) {
|
||||
buf_puts(&out, "<think>");
|
||||
buf_puts(&out, m->reasoning ? m->reasoning : "");
|
||||
buf_puts(&out, "</think>");
|
||||
} else {
|
||||
buf_puts(&out, "</think>");
|
||||
}
|
||||
buf_puts(&out, m->content ? m->content : "");
|
||||
append_laguna_tool_calls_text(&out, &m->calls, tool_orders);
|
||||
buf_puts(&out, "</assistant>\n");
|
||||
pending_assistant = false;
|
||||
}
|
||||
}
|
||||
if (pending_assistant) {
|
||||
buf_puts(&out, "<assistant>");
|
||||
buf_puts(&out, think ? "<think>" : "</think>");
|
||||
}
|
||||
return buf_take(&out);
|
||||
}
|
||||
|
||||
static char *render_live_tool_tail_for_syntax(server_model_syntax syntax,
|
||||
const chat_msgs *msgs, int start,
|
||||
const tool_schema_orders *tool_orders,
|
||||
@@ -2726,6 +2913,10 @@ static char *render_live_tool_tail_for_syntax(server_model_syntax syntax,
|
||||
if (syntax == SERVER_MODEL_SYNTAX_GLM) {
|
||||
return render_glm_live_tool_tail(msgs, start, tool_orders, think_mode);
|
||||
}
|
||||
if (syntax == SERVER_MODEL_SYNTAX_LAGUNA) {
|
||||
return render_laguna_live_tool_tail(msgs, start, tool_orders,
|
||||
think_mode);
|
||||
}
|
||||
return render_deepseek_live_tool_tail(msgs, start, think_mode);
|
||||
}
|
||||
|
||||
@@ -5103,7 +5294,8 @@ static bool parse_generated_message_ex_for_syntax(server_model_syntax syntax,
|
||||
char **content_out,
|
||||
char **reasoning_out,
|
||||
tool_calls *calls) {
|
||||
if (syntax == SERVER_MODEL_SYNTAX_GLM) {
|
||||
if (syntax == SERVER_MODEL_SYNTAX_GLM ||
|
||||
syntax == SERVER_MODEL_SYNTAX_LAGUNA) {
|
||||
return parse_glm_generated_message_ex(text, require_thinking_closed,
|
||||
content_out, reasoning_out,
|
||||
calls);
|
||||
@@ -10214,12 +10406,46 @@ static char *build_invalid_glm_tool_error_suffix(const request *r,
|
||||
return buf_take(&suffix);
|
||||
}
|
||||
|
||||
static char *build_invalid_laguna_tool_error_suffix(
|
||||
const request *r,
|
||||
const thinking_state *thinking,
|
||||
const char *detail) {
|
||||
buf tool_error = {0};
|
||||
buf_puts(&tool_error, "Tool error: invalid Laguna tool call");
|
||||
if (detail && detail[0]) {
|
||||
buf_puts(&tool_error, ": ");
|
||||
buf_puts(&tool_error, detail);
|
||||
}
|
||||
buf_puts(&tool_error,
|
||||
"\nThe previous assistant output was not executed because the "
|
||||
"<tool_call> syntax was malformed. Emit a new valid <tool_call>, "
|
||||
"or answer normally if no tool is needed.");
|
||||
|
||||
buf suffix = {0};
|
||||
if (r && ds4_think_mode_enabled(r->think_mode) &&
|
||||
thinking && thinking->inside) {
|
||||
buf_puts(&suffix, "</think>");
|
||||
}
|
||||
buf_puts(&suffix, "</assistant>\n<tool_response>");
|
||||
append_glm_tool_response_text(&suffix, tool_error.ptr ? tool_error.ptr : "");
|
||||
buf_puts(&suffix, "</tool_response>\n<assistant>");
|
||||
buf_puts(&suffix,
|
||||
r && ds4_think_mode_enabled(r->think_mode) ?
|
||||
"<think>" : "</think>");
|
||||
|
||||
buf_free(&tool_error);
|
||||
return buf_take(&suffix);
|
||||
}
|
||||
|
||||
static char *build_invalid_tool_call_error_suffix(const request *r,
|
||||
const thinking_state *thinking,
|
||||
const char *detail) {
|
||||
if (r && r->model_syntax == SERVER_MODEL_SYNTAX_GLM) {
|
||||
return build_invalid_glm_tool_error_suffix(r, thinking, detail);
|
||||
}
|
||||
if (r && r->model_syntax == SERVER_MODEL_SYNTAX_LAGUNA) {
|
||||
return build_invalid_laguna_tool_error_suffix(r, thinking, detail);
|
||||
}
|
||||
return build_invalid_dsml_tool_error_suffix(r, thinking, detail);
|
||||
}
|
||||
|
||||
@@ -10528,7 +10754,9 @@ static char *build_tool_checkpoint_suffix(const request *r, const char *content,
|
||||
buf_puts(&suffix, content ? content : "");
|
||||
append_tool_calls_text_for_syntax(&suffix, syntax, calls,
|
||||
r ? &r->tool_orders : NULL);
|
||||
if (syntax != SERVER_MODEL_SYNTAX_GLM) {
|
||||
if (syntax == SERVER_MODEL_SYNTAX_LAGUNA) {
|
||||
buf_puts(&suffix, "</assistant>\n");
|
||||
} else if (syntax != SERVER_MODEL_SYNTAX_GLM) {
|
||||
buf_puts(&suffix, "<|end▁of▁sentence|>");
|
||||
}
|
||||
return buf_take(&suffix);
|
||||
@@ -10558,7 +10786,9 @@ static char *build_responses_visible_assistant_suffix(const request *r,
|
||||
buf_puts(&suffix, content ? content : "");
|
||||
append_tool_calls_text_for_syntax(&suffix, syntax, calls,
|
||||
r ? &r->tool_orders : NULL);
|
||||
if (syntax != SERVER_MODEL_SYNTAX_GLM) {
|
||||
if (syntax == SERVER_MODEL_SYNTAX_LAGUNA) {
|
||||
buf_puts(&suffix, "</assistant>\n");
|
||||
} else if (syntax != SERVER_MODEL_SYNTAX_GLM) {
|
||||
buf_puts(&suffix, "<|end▁of▁sentence|>");
|
||||
}
|
||||
return buf_take(&suffix);
|
||||
@@ -10964,6 +11194,11 @@ static uint64_t server_next_sequence(server *s) {
|
||||
static void generate_job(server *s, server_slot *slot, job *j) {
|
||||
char err[160];
|
||||
err[0] = '\0';
|
||||
ds4_session_set_speculative_enabled(
|
||||
slot->session,
|
||||
!s->batched_mode &&
|
||||
j->req.temperature <= 0.0f &&
|
||||
getenv("DS4_MTP_SPEC_DISABLE") == NULL);
|
||||
const int old_pos = ds4_session_pos(slot->session);
|
||||
const int common = ds4_session_common_prefix(slot->session, &j->req.prompt);
|
||||
trace_cache_diag cache_diag = {0};
|
||||
@@ -11385,10 +11620,15 @@ decode_again:
|
||||
* only for knobs the client left out: an explicit request value
|
||||
* (e.g. temperature 0 from a benchmark harness) must win, or the
|
||||
* same greedy request returns different text on every call. */
|
||||
if (!j->req.temperature_set) temperature = DS4_DEFAULT_TEMPERATURE;
|
||||
if (!j->req.top_k_set) top_k = 0;
|
||||
if (!j->req.top_p_set) top_p = DS4_DEFAULT_TOP_P;
|
||||
if (!j->req.min_p_set) min_p = DS4_DEFAULT_MIN_P;
|
||||
float default_temperature, default_top_p, default_min_p;
|
||||
int default_top_k;
|
||||
ds4_engine_sampling_defaults(s->engine, &default_temperature,
|
||||
&default_top_k, &default_top_p,
|
||||
&default_min_p);
|
||||
if (!j->req.temperature_set) temperature = default_temperature;
|
||||
if (!j->req.top_k_set) top_k = default_top_k;
|
||||
if (!j->req.top_p_set) top_p = default_top_p;
|
||||
if (!j->req.min_p_set) min_p = default_min_p;
|
||||
}
|
||||
if (in_tool_call && !dsml_decode_state_uses_payload_sampling(dsml_state)) {
|
||||
temperature = 0.0f;
|
||||
@@ -11430,6 +11670,7 @@ decode_again:
|
||||
}
|
||||
|
||||
bool stop_decode = false;
|
||||
int committed_visible = 0;
|
||||
for (int ti = 0; ti < ntok && completion < max_tokens; ti++) {
|
||||
token = toks[ti];
|
||||
if (ds4_token_is_stop_for_think_mode(s->engine,
|
||||
@@ -11439,6 +11680,7 @@ decode_again:
|
||||
stop_decode = true;
|
||||
break;
|
||||
}
|
||||
committed_visible = ti + 1;
|
||||
|
||||
size_t piece_len = 0;
|
||||
char *piece = ds4_token_text(s->engine, token, &piece_len);
|
||||
@@ -11620,6 +11862,14 @@ decode_again:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (stop_decode && committed_visible < ntok) {
|
||||
/* A speculative verifier may have committed a suffix beyond the
|
||||
* visible stop/tool boundary. The next request must rebuild from
|
||||
* the protocol-visible transcript, not reuse those hidden rows. */
|
||||
pthread_mutex_lock(&s->inference_mu);
|
||||
ds4_session_invalidate(slot->session);
|
||||
pthread_mutex_unlock(&s->inference_mu);
|
||||
}
|
||||
if (stop_decode) break;
|
||||
}
|
||||
server_generation_leave(s);
|
||||
@@ -12342,7 +12592,13 @@ static bool send_model(server *s, int fd, const char *id) {
|
||||
static bool send_models(server *s, int fd) {
|
||||
buf b = {0};
|
||||
buf_puts(&b, "{\"object\":\"list\",\"data\":[");
|
||||
if (ds4_engine_is_glm_dsa(s->engine)) {
|
||||
if (ds4_engine_is_laguna(s->engine)) {
|
||||
append_model_json(&b, s, "laguna-s-2.1");
|
||||
buf_putc(&b, ',');
|
||||
append_model_json(&b, s, "laguna-s-2.1-chat");
|
||||
buf_putc(&b, ',');
|
||||
append_model_json(&b, s, "laguna-s-2.1-reasoner");
|
||||
} else if (ds4_engine_is_glm_dsa(s->engine)) {
|
||||
append_model_json(&b, s, "glm-5.2");
|
||||
buf_putc(&b, ',');
|
||||
append_model_json(&b, s, "glm-5.2-chat");
|
||||
@@ -12433,6 +12689,7 @@ static void *client_main(void *arg) {
|
||||
free(req.model);
|
||||
req.model = xstrdup(server_model_id_from_engine(s->engine));
|
||||
}
|
||||
request_apply_model_sampling_defaults(s->engine, &req);
|
||||
if (request_exceeds_context(&req, ctx_size)) {
|
||||
http_error_context_length_exceeded(fd, s->enable_cors, &req, req.prompt.len, ctx_size);
|
||||
request_free(&req);
|
||||
@@ -12660,6 +12917,7 @@ static server_config parse_options(int argc, char **argv) {
|
||||
.model_path = "ds4flash.gguf",
|
||||
.backend = default_server_backend(),
|
||||
.mtp_draft_tokens = 1,
|
||||
.dflash_draft_tokens = 0,
|
||||
.mtp_margin = 3.0f,
|
||||
},
|
||||
.host = "127.0.0.1",
|
||||
@@ -12700,6 +12958,16 @@ static server_config parse_options(int argc, char **argv) {
|
||||
c.engine.model_path = need_arg(&i, argc, argv, arg);
|
||||
} else if (!strcmp(arg, "--mtp")) {
|
||||
c.engine.mtp_path = need_arg(&i, argc, argv, arg);
|
||||
} else if (!strcmp(arg, "--dflash")) {
|
||||
c.engine.dflash_path = need_arg(&i, argc, argv, arg);
|
||||
} else if (!strcmp(arg, "--dflash-draft")) {
|
||||
c.engine.dflash_draft_tokens =
|
||||
parse_int_arg(need_arg(&i, argc, argv, arg), arg);
|
||||
} else if (!strcmp(arg, "--dflash-p-min")) {
|
||||
c.engine.dflash_p_min =
|
||||
parse_float_arg(
|
||||
need_arg(&i, argc, argv, arg), arg, 0.0f, 1.0f);
|
||||
c.engine.dflash_p_min_set = true;
|
||||
} else if (!strcmp(arg, "--mtp-draft")) {
|
||||
c.engine.mtp_draft_tokens = parse_int_arg(need_arg(&i, argc, argv, arg), arg);
|
||||
} else if (!strcmp(arg, "--mtp-margin")) {
|
||||
@@ -14435,11 +14703,17 @@ static void test_model_alias_thinking_controls(void) {
|
||||
TEST_ASSERT(model_alias_disables_thinking("glm-5.2-no-think"));
|
||||
TEST_ASSERT(model_alias_disables_thinking("zai/glm-5.2-chat"));
|
||||
TEST_ASSERT(!model_alias_disables_thinking("glm-5.2"));
|
||||
TEST_ASSERT(model_alias_disables_thinking("laguna-s-2.1-chat"));
|
||||
TEST_ASSERT(model_alias_disables_thinking("laguna-s-2.1-no-think"));
|
||||
TEST_ASSERT(!model_alias_disables_thinking("laguna-s-2.1"));
|
||||
TEST_ASSERT(model_alias_enables_thinking("deepseek-reasoner"));
|
||||
TEST_ASSERT(model_alias_enables_thinking("glm-5.2-reasoner"));
|
||||
TEST_ASSERT(model_alias_enables_thinking("zai/glm-5.2-reasoner"));
|
||||
TEST_ASSERT(model_alias_enables_thinking("laguna-s-2.1-reasoner"));
|
||||
TEST_ASSERT(server_model_alias_known("glm-5.2-chat"));
|
||||
TEST_ASSERT(server_model_alias_known("glm-5.2-reasoner"));
|
||||
TEST_ASSERT(server_model_alias_known("laguna-s-2.1-chat"));
|
||||
TEST_ASSERT(server_model_alias_known("laguna-s-2.1-reasoner"));
|
||||
}
|
||||
|
||||
static void test_api_thinking_controls_parse(void) {
|
||||
@@ -14680,6 +14954,114 @@ static void test_render_glm_preserves_reasoning_with_tools(void) {
|
||||
chat_msgs_free(&msgs);
|
||||
}
|
||||
|
||||
static void test_render_laguna_chat_prompt_text(void) {
|
||||
chat_msgs msgs = {0};
|
||||
chat_msg user = {0};
|
||||
user.role = xstrdup("user");
|
||||
user.content = xstrdup("Hello");
|
||||
chat_msgs_push(&msgs, user);
|
||||
|
||||
char *prompt = render_chat_prompt_text_for_syntax(
|
||||
SERVER_MODEL_SYNTAX_LAGUNA, &msgs, NULL, NULL, DS4_THINK_NONE);
|
||||
const char *expected =
|
||||
"〈|EOS|〉"
|
||||
"<system>You are a helpful, conversationally-fluent assistant made by Poolside. "
|
||||
"You are here to be helpful to users through natural language conversations.</system>\n"
|
||||
"<user>Hello</user>\n<assistant></think>";
|
||||
TEST_ASSERT(prompt != NULL);
|
||||
TEST_ASSERT(!strcmp(prompt, expected));
|
||||
free(prompt);
|
||||
|
||||
prompt = render_chat_prompt_text_for_syntax(
|
||||
SERVER_MODEL_SYNTAX_LAGUNA, &msgs, NULL, NULL, DS4_THINK_HIGH);
|
||||
TEST_ASSERT(strstr(prompt, "<assistant><think>") != NULL);
|
||||
free(prompt);
|
||||
chat_msgs_free(&msgs);
|
||||
}
|
||||
|
||||
static void test_render_laguna_tools_and_reasoning(void) {
|
||||
chat_msgs msgs = {0};
|
||||
chat_msg sys = {0};
|
||||
sys.role = xstrdup("system");
|
||||
sys.content = xstrdup("Code carefully. \n");
|
||||
chat_msgs_push(&msgs, sys);
|
||||
chat_msg user1 = {0};
|
||||
user1.role = xstrdup("user");
|
||||
user1.content = xstrdup("pwd");
|
||||
chat_msgs_push(&msgs, user1);
|
||||
chat_msg assistant = {0};
|
||||
assistant.role = xstrdup("assistant");
|
||||
assistant.reasoning = xstrdup("Use the shell.");
|
||||
tool_call tc = {0};
|
||||
tc.name = xstrdup("bash");
|
||||
tc.arguments = xstrdup("{\"command\":\"pwd\"}");
|
||||
tool_calls_push(&assistant.calls, tc);
|
||||
chat_msgs_push(&msgs, assistant);
|
||||
chat_msg tool = {0};
|
||||
tool.role = xstrdup("tool");
|
||||
tool.content = xstrdup("/tmp");
|
||||
chat_msgs_push(&msgs, tool);
|
||||
|
||||
tool_schema_orders orders = make_bash_order();
|
||||
const char *schema =
|
||||
"{\"type\":\"function\",\"function\":{\"name\":\"bash\"}}";
|
||||
char *prompt = render_chat_prompt_text_for_syntax(
|
||||
SERVER_MODEL_SYNTAX_LAGUNA, &msgs, schema, &orders, DS4_THINK_HIGH);
|
||||
TEST_ASSERT(prompt != NULL);
|
||||
TEST_ASSERT(strstr(prompt,
|
||||
"<system>Code carefully.\n\n### Tools\n\n") != NULL);
|
||||
TEST_ASSERT(strstr(prompt, "<available_tools>\n") != NULL);
|
||||
TEST_ASSERT(strstr(prompt, "</available_tools></system>\n") != NULL);
|
||||
TEST_ASSERT(strstr(prompt,
|
||||
"<assistant><think>Use the shell.</think>"
|
||||
"<tool_call>bash<arg_key>command</arg_key>"
|
||||
"<arg_value>pwd</arg_value></tool_call></assistant>\n") != NULL);
|
||||
TEST_ASSERT(strstr(prompt,
|
||||
"<tool_response>/tmp</tool_response>\n<assistant><think>") != NULL);
|
||||
|
||||
free(prompt);
|
||||
tool_schema_orders_free(&orders);
|
||||
chat_msgs_free(&msgs);
|
||||
}
|
||||
|
||||
static void test_render_laguna_live_tool_tail(void) {
|
||||
chat_msgs msgs = {0};
|
||||
chat_msg tool = {0};
|
||||
tool.role = xstrdup("tool");
|
||||
tool.content = xstrdup("ok");
|
||||
chat_msgs_push(&msgs, tool);
|
||||
char *tail = render_live_tool_tail_for_syntax(
|
||||
SERVER_MODEL_SYNTAX_LAGUNA, &msgs, 0, NULL, DS4_THINK_HIGH);
|
||||
TEST_ASSERT(tail != NULL);
|
||||
TEST_ASSERT(!strcmp(tail,
|
||||
"</assistant>\n<tool_response>ok</tool_response>\n"
|
||||
"<assistant><think>"));
|
||||
free(tail);
|
||||
chat_msgs_free(&msgs);
|
||||
}
|
||||
|
||||
static void test_parse_laguna_tool_call_message(void) {
|
||||
char *content = NULL;
|
||||
char *reasoning = NULL;
|
||||
tool_calls calls = {0};
|
||||
const char *raw =
|
||||
"plan</think>Done<tool_call>bash"
|
||||
"<arg_key>command</arg_key><arg_value>pwd</arg_value>"
|
||||
"</tool_call>";
|
||||
TEST_ASSERT(parse_generated_message_ex_for_syntax(
|
||||
SERVER_MODEL_SYNTAX_LAGUNA, raw, true,
|
||||
&content, &reasoning, &calls));
|
||||
TEST_ASSERT(reasoning && !strcmp(reasoning, "plan"));
|
||||
TEST_ASSERT(content && !strcmp(content, "Done"));
|
||||
TEST_ASSERT(calls.len == 1);
|
||||
TEST_ASSERT(calls.v[0].name && !strcmp(calls.v[0].name, "bash"));
|
||||
TEST_ASSERT(calls.v[0].arguments &&
|
||||
!strcmp(calls.v[0].arguments, "{\"command\": \"pwd\"}"));
|
||||
free(content);
|
||||
free(reasoning);
|
||||
tool_calls_free(&calls);
|
||||
}
|
||||
|
||||
static void test_dsml_tool_args_preserve_call_order(void) {
|
||||
tool_calls calls = make_swapped_bash_call();
|
||||
buf b = {0};
|
||||
@@ -17425,6 +17807,10 @@ static void ds4_server_unit_tests_run(void) {
|
||||
test_render_glm_chat_prompt_text();
|
||||
test_render_glm_drops_old_reasoning_without_tools();
|
||||
test_render_glm_preserves_reasoning_with_tools();
|
||||
test_render_laguna_chat_prompt_text();
|
||||
test_render_laguna_tools_and_reasoning();
|
||||
test_render_laguna_live_tool_tail();
|
||||
test_parse_laguna_tool_call_message();
|
||||
test_tool_schema_order_from_anthropic_schema();
|
||||
test_tool_schema_order_from_openai_tools();
|
||||
test_tool_schema_order_from_responses_tool_search();
|
||||
|
||||
@@ -15,6 +15,9 @@ calling hosted APIs:
|
||||
|
||||
- `data/glm52-openrouter-100`: 100 GLM 5.2 continuations collected through
|
||||
OpenRouter `z-ai/glm-5.2` with `top_logprobs=20`.
|
||||
- `data/laguna-openrouter-100`: 100 Laguna S 2.1 continuations collected
|
||||
through OpenRouter `poolside/laguna-s-2.1`. Poolside's endpoint does not
|
||||
expose output-token logprobs.
|
||||
- `data/flash`: 100 DeepSeek V4 Flash continuations collected from the official
|
||||
DeepSeek API with `top_logprobs=20`.
|
||||
- `data/pro`: 100 DeepSeek V4 PRO continuations collected from the official
|
||||
@@ -58,6 +61,30 @@ python3 gguf-tools/quality-testing/collect_official.py \
|
||||
--reasoning-effort none
|
||||
```
|
||||
|
||||
For Laguna S 2.1 through OpenRouter:
|
||||
|
||||
```sh
|
||||
export OPENROUTER_API_KEY=...
|
||||
python3 gguf-tools/quality-testing/collect_official.py \
|
||||
--model poolside/laguna-s-2.1 \
|
||||
--endpoint https://openrouter.ai/api/v1/chat/completions \
|
||||
--api-key-env OPENROUTER_API_KEY \
|
||||
--prompts gguf-tools/quality-testing/prompts.jsonl \
|
||||
--out gguf-tools/quality-testing/data/laguna-openrouter-100 \
|
||||
--count 100 \
|
||||
--max-tokens 24 \
|
||||
--top-logprobs 0 \
|
||||
--token-limit-field max_tokens \
|
||||
--thinking omit \
|
||||
--reasoning-effort none
|
||||
```
|
||||
|
||||
The Laguna fixture supports local target-token NLL, first-token agreement, and
|
||||
greedy-prefix comparison. API logprob-delta and top-N agreement fields remain
|
||||
zero because the Poolside endpoint does not return logprobs. Its raw response
|
||||
files are retained for provenance but omitted from `manifest.tsv`, avoiding
|
||||
attempts to parse unavailable API logprobs during local scoring.
|
||||
|
||||
Use one output directory per official model. The default model is Flash, so
|
||||
`data/flash` is the recommended path for Flash continuations. For PRO:
|
||||
|
||||
@@ -78,6 +105,9 @@ The script writes:
|
||||
- `data/<model>/responses/case_*.json`
|
||||
- `data/<model>/manifest.tsv`
|
||||
|
||||
The response path is the optional fourth manifest column and is included only
|
||||
when `--top-logprobs` is greater than zero.
|
||||
|
||||
The prompt list is tracked in `prompts.jsonl`. Curated fixture directories are
|
||||
also tracked after review; ad-hoc API collection directories should stay
|
||||
untracked until they are intentionally promoted into the release QA set.
|
||||
@@ -108,7 +138,8 @@ gguf-tools/quality-testing/score_official \
|
||||
|
||||
Use `data/flash/manifest.tsv` for Flash GGUFs,
|
||||
`data/glm52-openrouter-100/manifest.tsv` for GLM 5.2 GGUFs, and
|
||||
`data/pro/manifest.tsv` for PRO GGUFs. The scorer and comparator do not care
|
||||
`data/laguna-openrouter-100/manifest.tsv` for Laguna S 2.1 GGUFs, and
|
||||
`data/pro/manifest.tsv` for PRO GGUFs. The scorer and comparator do not care
|
||||
which model produced the manifest; the manifest path selects the continuation
|
||||
set.
|
||||
|
||||
|
||||
@@ -139,10 +139,11 @@ def request_one(
|
||||
"model": model,
|
||||
"messages": [{"role": "user", "content": prompt}],
|
||||
"temperature": 0,
|
||||
"logprobs": True,
|
||||
"top_logprobs": top_logprobs,
|
||||
"stream": False,
|
||||
}
|
||||
if top_logprobs > 0:
|
||||
payload["logprobs"] = True
|
||||
payload["top_logprobs"] = top_logprobs
|
||||
payload[token_limit_field] = max_tokens
|
||||
if thinking != "omit":
|
||||
payload["thinking"] = {"type": thinking}
|
||||
@@ -304,13 +305,20 @@ def main() -> int:
|
||||
prompt_path.write_text(prompt, encoding="utf-8")
|
||||
cont_path.write_text(content, encoding="utf-8")
|
||||
resp_path.write_text(json.dumps(response, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||
rows.append((case_id, prompt_path, cont_path, resp_path))
|
||||
rows.append((case_id, prompt_path, cont_path,
|
||||
resp_path if args.top_logprobs > 0 else None))
|
||||
time.sleep(0.05)
|
||||
|
||||
with manifest.open("w", encoding="utf-8") as fp:
|
||||
fp.write("# id\tprompt_file\tcontinuation_file\tresponse_file\n")
|
||||
fp.write("# id\tprompt_file\tcontinuation_file")
|
||||
if args.top_logprobs > 0:
|
||||
fp.write("\tresponse_file")
|
||||
fp.write("\n")
|
||||
for row in rows:
|
||||
fp.write("\t".join([row[0], str(row[1]), str(row[2]), str(row[3])]) + "\n")
|
||||
fields = [row[0], str(row[1]), str(row[2])]
|
||||
if row[3] is not None:
|
||||
fields.append(str(row[3]))
|
||||
fp.write("\t".join(fields) + "\n")
|
||||
print(f"wrote {manifest}", file=sys.stderr)
|
||||
return 0
|
||||
|
||||
|
||||
@@ -5,6 +5,9 @@ safe to commit and use in release QA.
|
||||
|
||||
- `glm52-openrouter-100`: 100 GLM 5.2 OpenRouter continuations with API
|
||||
top-logprob slices.
|
||||
- `laguna-openrouter-100`: 100 Laguna S 2.1 OpenRouter continuations. Poolside's
|
||||
endpoint does not expose output-token logprobs, so these support
|
||||
teacher-forced continuation scoring only.
|
||||
- `flash`: 100 DeepSeek V4 Flash official continuations with API top-logprob
|
||||
slices.
|
||||
- `pro`: 100 DeepSeek V4 PRO official continuations with API top-logprob
|
||||
@@ -14,8 +17,10 @@ Each fixture directory contains:
|
||||
|
||||
- `prompts/case_*.txt`: exact user prompts.
|
||||
- `continuations/case_*.txt`: deterministic hosted-model continuations.
|
||||
- `responses/case_*.json`: raw hosted responses, including logprob slices.
|
||||
- `manifest.tsv`: paths consumed by `score_official`.
|
||||
- `responses/case_*.json`: raw hosted responses, including logprob slices when
|
||||
the endpoint provides them.
|
||||
- `manifest.tsv`: paths consumed by `score_official`; the optional raw-response
|
||||
path is present only for fixtures with API logprobs.
|
||||
|
||||
DeepSeek V4 Flash smoke vectors are also tracked in `tests/test-vectors/` and
|
||||
are run by `./ds4_test --logprob-vectors`.
|
||||
@@ -0,0 +1,3 @@
|
||||
# B-Tree Insertion Explained
|
||||
|
||||
B-tree insertion is a fundamental operation that maintains the balanced structure of B-trees. Let me
|
||||
@@ -0,0 +1,4 @@
|
||||
# TCP Echo Server Design for Slow Clients
|
||||
|
||||
## Overview
|
||||
A TCP echo server that efficiently handles slow clients by implementing buffering
|
||||
@@ -0,0 +1 @@
|
||||
When comparing memory-mapped (mmaped) model weights with copying all weights into private buffers on macOS, several key factors come
|
||||
@@ -0,0 +1 @@
|
||||
RMSNorm is a normalization technique designed to stabilize training by scaling the input based on its root mean square (RMS) value
|
||||
@@ -0,0 +1 @@
|
||||
KV (Key-Value) cache checkpointing is a technique that helps manage memory and computation in **agentic sessions**—long
|
||||
@@ -0,0 +1,3 @@
|
||||
To implement the push operation for a binary heap (specifically a min-heap), follow this pseudocode:
|
||||
|
||||
```pseudocode
|
||||
@@ -0,0 +1 @@
|
||||
The tradeoffs between **top-p (nucleus) sampling** and **temperature-zero (greedy) decoding** revolve around
|
||||
@@ -0,0 +1 @@
|
||||
Speculative decoding is a technique that accelerates inference in large language models (LLMs) by using a faster **draft model
|
||||
@@ -0,0 +1 @@
|
||||
A GPU prefill path can be faster than single-token decode due to several key factors related to computational efficiency, memory access
|
||||
@@ -0,0 +1,3 @@
|
||||
Here are three invariants for a tokenizer's special-token table:
|
||||
|
||||
1. **Uniqueness of Special Tokens**:
|
||||
@@ -0,0 +1 @@
|
||||
L'inserimento in un B-albero (B-tree) è un processo strutturato per mant
|
||||
@@ -0,0 +1,3 @@
|
||||
Ecco un progetto conciso per un server TCP echo con client lenti:
|
||||
|
||||
**Server (echo
|
||||
@@ -0,0 +1 @@
|
||||
Ecco un confronto dettagliato tra **mmap dei pesi** e **copia completa dei
|
||||
@@ -0,0 +1 @@
|
||||
RMSNorm (Root Mean Square Layer Normalization) richiede la somma dei quadrati e il riscalamento per
|
||||
@@ -0,0 +1 @@
|
||||
La cache KV (Key-Value) su disco è un meccanismo di memorizzazione che permette di
|
||||
@@ -0,0 +1 @@
|
||||
Per inserire un elemento in un heap binario (assumendo un **max-heap**), seguiamo quest
|
||||
@@ -0,0 +1 @@
|
||||
L'approccio **top-p (nucleus sampling)** e la **temperatura zero** sono due strateg
|
||||
@@ -0,0 +1 @@
|
||||
La **decodifica speculativa** può mantenere l'output esatto grazie a mec
|
||||
@@ -0,0 +1 @@
|
||||
Il **prefill GPU** può essere più veloce del **decode token singolo** (o
|
||||
@@ -0,0 +1 @@
|
||||
Ecco tre invarianti che potrebbero essere utili per una tabella di token speciali:
|
||||
@@ -0,0 +1 @@
|
||||
Binary search efficiently finds the insertion point in a sorted array by narrowing down the search space through comparisons. Here's a step
|
||||
@@ -0,0 +1,4 @@
|
||||
Here's a concise C function that clamps an integer to a specified range:
|
||||
|
||||
```c
|
||||
int clamp(int x
|
||||
@@ -0,0 +1,4 @@
|
||||
**Mutex vs. Atomic Counter: Key Differences**
|
||||
|
||||
1. **Purpose and Scope**:
|
||||
- **Mutex**:
|
||||
@@ -0,0 +1 @@
|
||||
**Backpressure** in a network server is a flow-control mechanism that prevents a fast data producer from overwhelming a slower consumer.
|
||||
@@ -0,0 +1 @@
|
||||
A safe format for storing a model checkpoint header should prioritize **reliability**, **versioning**, **compatibility**, and **
|
||||
@@ -0,0 +1 @@
|
||||
A **ring buffer** (or circular buffer) is a fixed-size data structure that uses a circular memory layout to efficiently
|
||||
@@ -0,0 +1,4 @@
|
||||
# Long-Context Prompt Chunking Test Plan
|
||||
|
||||
## Objective
|
||||
Verify that large prompts are correctly split into manageable chunks while maintaining
|
||||
@@ -0,0 +1 @@
|
||||
The **importance matrix** in low-bit quantization is a critical component used to prioritize which elements of a tensor (e.g
|
||||
@@ -0,0 +1 @@
|
||||
Grouped Mixture of Experts (MoE) expert execution enhances prefill performance through several key optimizations, particularly in distributed computing
|
||||
@@ -0,0 +1,3 @@
|
||||
To compare two model quantizations without relying on a single answer, you can use the following structured approach:
|
||||
|
||||
###
|
||||
@@ -0,0 +1 @@
|
||||
La ricerca binaria del punto di inserimento in una lista ordinata è un algoritmo che determin
|
||||
@@ -0,0 +1,3 @@
|
||||
Ecco una funzione C che limita un intero a un intervallo specificato:
|
||||
|
||||
```c
|
||||
@@ -0,0 +1 @@
|
||||
La differenza tra **mutex** e **contatore atomico** risiede nell'approccio
|
||||
@@ -0,0 +1,3 @@
|
||||
**Cosa significa "backpressure" in un server di rete?**
|
||||
|
||||
Il **backpressure** è un
|
||||
@@ -0,0 +1 @@
|
||||
Un formato sicuro per salvare l'header di un checkpoint di un modello dovrebbe garantire
|
||||
@@ -0,0 +1 @@
|
||||
Un **ring buffer** (o buffer circolare) è una struttura dati che utilizza una sing
|
||||
@@ -0,0 +1,5 @@
|
||||
Ecco un piano conciso per testare il **chunking di prompt lunghi**:
|
||||
|
||||
---
|
||||
|
||||
### **
|
||||
@@ -0,0 +1 @@
|
||||
La **matrice di importanza** nella quantizzazione low-bit è un concetto utilizzato per determin
|
||||
@@ -0,0 +1 @@
|
||||
Raggruppare gli esperti in un modello **Mixture of Experts (MoE)** pu
|
||||
@@ -0,0 +1 @@
|
||||
Per confrontare due quantizzazioni in modo robusto e non affidarsi a una singola risposta, pu
|
||||
@@ -0,0 +1,3 @@
|
||||
When addressing generation slowdowns at long contexts, the following five measurements should be prioritized to diagnose the issue:
|
||||
|
||||
1. **
|
||||
@@ -0,0 +1 @@
|
||||
Small logit differences can significantly alter greedy continuation because the **softmax function**, which converts logits into probabilities, amplifies differences through
|
||||
@@ -0,0 +1 @@
|
||||
Online softmax in attention is a technique that allows the softmax computation to be done incrementally, without needing to store all intermediate values
|
||||
@@ -0,0 +1,5 @@
|
||||
Here's a minimal JSON schema for a tool call with a name and arguments:
|
||||
|
||||
```json
|
||||
{
|
||||
"
|
||||
@@ -0,0 +1 @@
|
||||
To test that a file-backed `mmap` is not repeatedly remapped, you can use system call tracing to monitor `
|
||||
@@ -0,0 +1 @@
|
||||
In the context of graphics rendering with Metal (and similar APIs like Vulkan or DirectX), using one large buffer can be less
|
||||
@@ -0,0 +1 @@
|
||||
In a **Mixture-of-Experts (MoE)** layer, the **router** plays a critical role in dynamically
|
||||
@@ -0,0 +1 @@
|
||||
The difference between raw Key-Value (KV) rows and compressed KV rows lies in their storage format, efficiency, and use
|
||||
@@ -0,0 +1,4 @@
|
||||
# Quantized Model Instruction-Following Validation Checklist
|
||||
|
||||
## Pre-Quantization Baseline Testing
|
||||
- [ ] **Establish baseline
|
||||
@@ -0,0 +1 @@
|
||||
Evaluating 50 prompts is more informative than a single favorite prompt because it provides a **broader, more robust understanding
|
||||
@@ -0,0 +1 @@
|
||||
Ecco cinque misure iniziali per diagnosticare e migiorare le prestazioni del decoding in
|
||||
@@ -0,0 +1 @@
|
||||
Le piccole differenze nei logit possono cambiare una continuazione greedy perché il metodo
|
||||
@@ -0,0 +1 @@
|
||||
L'attenzione con **online softmax** è una tecnica utilizzata in modelli di intell
|
||||
@@ -0,0 +1,4 @@
|
||||
Ecco uno schema JSON minimo per una tool call con nome e argomenti:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -0,0 +1 @@
|
||||
Per verificare che un file mappato con `mmap` non venga rimappato ripetutamente
|
||||
@@ -0,0 +1 @@
|
||||
Un **grande buffer Metal** (ad esempio, un buffer di grandi dimensioni utilizzato in
|
||||
@@ -0,0 +1 @@
|
||||
Il **router** in un modello **layer mixture-of-experts (MoE)** svolge un ruolo
|
||||
@@ -0,0 +1 @@
|
||||
La differenza tra **righe KV raw** e **righe KV compresse** risiede nel modo in
|
||||
@@ -0,0 +1 @@
|
||||
Ecco una checklist per validare che un modello quantizzato segua ancora le istruzioni
|
||||
@@ -0,0 +1 @@
|
||||
Valutare 50 prompt invece di un singolo "prompt preferito" può offrire
|
||||
@@ -0,0 +1 @@
|
||||
To find the median of three numbers, we can leverage the fact that the median is the middle value when the numbers are
|
||||
@@ -0,0 +1 @@
|
||||
Sorted arrays enable faster membership tests using **binary search** because their ordered structure allows the algorithm to eliminate half of the remaining
|
||||
@@ -0,0 +1 @@
|
||||
**Eventual consistency** in a distributed database is a consistency model that guarantees that, *if no new updates are made to
|
||||
@@ -0,0 +1,3 @@
|
||||
Here's a simple example of a race condition in C using two threads that increment a shared variable without synchronization:
|
||||
|
||||
```
|
||||
@@ -0,0 +1,2 @@
|
||||
**Latency** and **throughput** are two key performance metrics that describe different aspects of system or network efficiency:
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
A trie (prefix tree) represents a set of strings by storing each character of the strings in a tree structure, where
|
||||
@@ -0,0 +1,3 @@
|
||||
To test a command-line parser effectively, especially with edge cases, follow this structured approach:
|
||||
|
||||
### 1. **
|
||||
@@ -0,0 +1,3 @@
|
||||
The difference between mmap page residency and virtual address space size arises due to several mechanisms in memory management:
|
||||
|
||||
1. **
|
||||
@@ -0,0 +1 @@
|
||||
Quantization error, the discrepancy introduced when converting continuous values to discrete representations, disproportionately impacts **rare experts** compared to **common
|
||||
@@ -0,0 +1,5 @@
|
||||
Here's a concise checklist for reviewing PRs involving memory lifetimes:
|
||||
|
||||
### Memory Lifetime Review Checklist
|
||||
|
||||
**1. Ownership &
|
||||
@@ -0,0 +1,3 @@
|
||||
Ecco una piccola funzione Python per calcolare la mediana di tre numeri:
|
||||
|
||||
```
|
||||
@@ -0,0 +1 @@
|
||||
Gli array ordinati rendono più veloce la ricerca di appartenenza grazie all'
|
||||
@@ -0,0 +1 @@
|
||||
La **consistenza eventuale** (o *eventual consistency*) in un database distribuito è un model
|
||||
@@ -0,0 +1 @@
|
||||
Ecco un esempio semplice di **race condition** in C utilizzando thread POSIX. La situ
|
||||
@@ -0,0 +1 @@
|
||||
La **latenza** e il **throughput** sono due metriche fondamentali per valutare le prest
|
||||
@@ -0,0 +1 @@
|
||||
Un **trie** (o albero di prefissi) rappresenta un insieme di string
|
||||
@@ -0,0 +1 @@
|
||||
Ecco una guida completa per testare un parser da riga di comando utilizzando casi lim
|
||||
@@ -0,0 +1 @@
|
||||
La dimensione della **residenza** (resident set size, RSS) di una mappa di memoria
|
||||
@@ -0,0 +1 @@
|
||||
L'errore di quantizzazione può colpire più gli "esperti rari"
|
||||
@@ -0,0 +1,3 @@
|
||||
Ecco una checklist per revisionare la gestione della memoria in una Pull Request (PR):
|
||||
|
||||
### **
|
||||
@@ -0,0 +1 @@
|
||||
A good benchmark should measure performance under realistic and consistent conditions, providing standardized metrics that are reproducible, relevant to the intended use
|
||||
@@ -0,0 +1,4 @@
|
||||
Here’s a completed version of the C comment with common documentation for a lock:
|
||||
|
||||
```c
|
||||
/* This lock protects
|
||||
@@ -0,0 +1,3 @@
|
||||
The main advantage of the cache is to **accelerare l'accesso ai dati**.
|
||||
|
||||
Completed sentence:
|
||||
Loaded 100 of 337 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user