Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 28 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,31 @@ CC ?= cc
CFLAGS ?= -O2 -std=gnu11 -Wall -Wextra
LDLIBS := -lm -lpthread

# The interpreter for the three Python recipes below, resolved by RUNNING a
# candidate rather than by looking one up. On Windows the name `python3` on
# PATH is usually the Microsoft Store App Execution Alias: a zero-byte
# reparse point that exists, exits 49, and prints an advert for the Store
# instead of running anything, so a recipe reports its own tool broken. Same
# finding as tests/run.sh, which shims PATH because it has 49 call sites and
# subprocesses to cover; three recipes want a name instead. PATH set inside
# run.sh does not reach a recipe make runs itself, which is why this is here
# and not there.
#
# Windows installs the versionless `python`, and `py` besides, so a working
# interpreter is normally there under another name. If none answers this
# stays `python3` and the recipe fails loudly at first use, which is right
# for a build target: unlike the suite, there is nothing here to skip.
#
# Recursive rather than `:=` so the probe runs only when a recipe actually
# expands $(PY). Immediate assignment costs every `make` invocation two
# process spawns, `make clean` and a no-op build included, and on the box
# this was found the first spawn is the Store alias itself: measured 196 ms
# added per invocation, which nearly triples a no-op make. The fallback
# lives inside the shell for the same reason, since an `ifeq` on $(PY)
# would force the expansion at parse time and undo it.
PY = $(shell for p in python3 python py; do \
"$$p" -c '' >/dev/null 2>&1 && { echo "$$p"; exit 0; }; done; echo python3)

# The target triple rather than `uname -m`, because those differ the moment
# anyone cross-compiles: building for Windows from this ARM laptop, uname
# says arm64 and would put the NEON translation unit into an x86 binary
Expand Down Expand Up @@ -299,7 +324,7 @@ check: test
# libwaste as a shared object rather than the archive the CLI links, and
# because it is the one part of this repo that is not C.
serve-check: libwaste.$(SOEXT)
@python3 -m unittest discover -s tests/serve -t . -p "test_*.py"
@$(PY) -m unittest discover -s tests/serve -t . -p "test_*.py"

# Sanitizers. Separate targets rather than a flag on `make`, because they
# need a full rebuild: mixing instrumented and uninstrumented objects
Expand All @@ -320,7 +345,7 @@ asan:
$(MAKE) --no-print-directory clean; exit $$rc

fuzz: test
@python3 tools/fuzz_container.py --runs $(FUZZ_RUNS)
@$(PY) tools/fuzz_container.py --runs $(FUZZ_RUNS)

FUZZ_RUNS ?= 200

Expand All @@ -332,7 +357,7 @@ fuzz-asan:
CFLAGS="-std=gnu11 -Wall -Wextra -DVQ_SUPER=$(VQ_SUPER) -MMD -MP $(SAN_FLAGS)" \
LDLIBS="-lm -lpthread $(SAN_FLAGS)"
@rc=0; ASAN_OPTIONS=detect_leaks=0 UBSAN_OPTIONS=print_stacktrace=1 \
python3 tools/fuzz_container.py --runs $(FUZZ_RUNS) || rc=$$?; \
$(PY) tools/fuzz_container.py --runs $(FUZZ_RUNS) || rc=$$?; \
$(MAKE) --no-print-directory clean; exit $$rc

.PHONY: all test check serve-check clean asan fuzz fuzz-asan
28 changes: 24 additions & 4 deletions tools/fetch_weights.sh
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,26 @@

set -uo pipefail

# python3, resolved by RUNNING a candidate rather than by looking one up.
# On Windows the name on PATH is usually the Microsoft Store App Execution
# Alias: a zero-byte reparse point that exists, exits 49, and prints an
# advert for the Store instead of running anything. Same finding as
# tests/run.sh, which shims PATH because it has 49 call sites and
# subprocesses to cover; the handful here get a name and pass it down.
#
# Windows installs the versionless `python`, and `py` besides, so a working
# interpreter is normally there under another name. If none answers, this
# stays `python3` and the script fails loudly at first use, which is right
# here: unlike the suite there is nothing to skip, and a run that cannot
# read its own index must stop rather than carry on.
if [ -z "${PY:-}" ]; then
for _cand in python3 python py; do
"$_cand" -c '' >/dev/null 2>&1 && { PY="$_cand"; break; }
done
: "${PY:=python3}"
fi
export PY

REPO="${REPO:-moonshotai/Kimi-K3}"
DEST="${DEST:-/Volumes/WasteDisk/k3}"
JOBS="${JOBS:-3}"
Expand Down Expand Up @@ -185,7 +205,7 @@ if [ "$DRY" = 0 ] && [ "$CHECK_ONLY" = 0 ]; then
# missed preprocessor_config.json, so the image normalization was the
# CLIP convention for a day when the release states mean = std = 0.5.
# A whitelist cannot report what it never knew to ask for.
SMALL=$(hcurl -sfL --max-time 60 "$API" 2>/dev/null | python3 -c '
SMALL=$(hcurl -sfL --max-time 60 "$API" 2>/dev/null | "$PY" -c '
import json, sys
try:
d = json.load(sys.stdin)
Expand Down Expand Up @@ -247,14 +267,14 @@ fi
# build that cannot be tested from here, and deleting a byte that must never
# appear in a safetensors filename is checkable by reading. It is a no-op
# everywhere else.
python3 - "$DEST/model.safetensors.index.json" <<'PY' | tr -d '\r' > "$DEST/.shards"
"$PY" - "$DEST/model.safetensors.index.json" <<'PY' | tr -d '\r' > "$DEST/.shards"
import json, sys
idx = json.load(open(sys.argv[1]))
for s in sorted(set(idx["weight_map"].values())):
print(s)
PY
TOTAL=$(wc -l < "$DEST/.shards" | tr -d ' ')
TOTAL_BYTES=$(python3 - "$DEST/model.safetensors.index.json" <<'PY' | tr -d '\r'
TOTAL_BYTES=$("$PY" - "$DEST/model.safetensors.index.json" <<'PY' | tr -d '\r'
import json, sys
print(json.load(open(sys.argv[1])).get("metadata", {}).get("total_size", 0))
PY
Expand All @@ -272,7 +292,7 @@ while read -r f; do
done < "$DEST/.shards"

log "repo $REPO -> $DEST (stat: $STAT_MODE)"
python3 - "$TOTAL_BYTES" "$have_bytes" "$(free_kb)" "$TOTAL" "$have" <<'PY'
"$PY" - "$TOTAL_BYTES" "$have_bytes" "$(free_kb)" "$TOTAL" "$have" <<'PY'
import sys
tot, got, availkb, n, nhave = (int(x) for x in sys.argv[1:6])
g = 1 << 30
Expand Down
30 changes: 26 additions & 4 deletions tools/pipeline.sh
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,28 @@
# be downloaded again — so it stays something the caller asks for.

set -uo pipefail

# python3, resolved by RUNNING a candidate rather than by looking one up.
# On Windows the name on PATH is usually the Microsoft Store App Execution
# Alias: a zero-byte reparse point that exists, exits 49, and prints an
# advert for the Store instead of running anything. Same finding as
# tests/run.sh, which shims PATH because it has 49 call sites and
# subprocesses to cover; the handful here get a name and pass it down.
#
# Windows installs the versionless `python`, and `py` besides, so a working
# interpreter is normally there under another name. If none answers, this
# stays `python3` and the script fails loudly at first use, which is right
# here: unlike the suite there is nothing to skip, and a run that cannot
# read its own index must stop rather than carry on.
# Exported rather than local, so the fetch_weights.sh this drives inherits
# the same answer instead of resolving it again per stage.
if [ -z "${PY:-}" ]; then
for _cand in python3 python py; do
"$_cand" -c '' >/dev/null 2>&1 && { PY="$_cand"; break; }
done
: "${PY:=python3}"
fi
export PY
cd "$(dirname "$0")/.."

SRC="${SRC:-/Volumes/WasteDisk/k3}"
Expand All @@ -61,7 +83,7 @@ say "=== pipeline start ==="
say "src $SRC -> out $OUT, $JOBS conversion processes"

# ---- 1. download ---------------------------------------------------------
NEED=$(python3 -c "
NEED=$("$PY" -c "
import json;print(len(set(json.load(open('$SRC/model.safetensors.index.json'))['weight_map'].values())))" 2>/dev/null || echo 0)
[ "$NEED" -gt 0 ] || die "download (no index at $SRC)"

Expand All @@ -83,15 +105,15 @@ say "stage 2: probe — ${FREE} GB free on the target volume"

# The first MoE layer. K3 nests the text model, and the dense prefix has no
# experts to round-trip, so neither is a detail this can guess at.
PROBE=$(python3 -c "
PROBE=$("$PY" -c "
import json
c = json.load(open('$SRC/config.json'))
c = c.get('text_config', c)
print(c.get('first_k_dense_replace', 0))" 2>/dev/null || echo "")
[ -n "$PROBE" ] || die "probe (no config.json at $SRC)"

probe_done() {
python3 -c "
"$PY" -c "
import json, sys
try:
m = json.load(open('$OUT/manifest.json'))
Expand Down Expand Up @@ -156,7 +178,7 @@ say " prompt ids: $IDS"
$UV tools/kimi_ref.py --container "$OUT" --tokens 0 --prompt-ids "$IDS" \
--dump "$RUN/logits_ref.bin" >>"$LOG" 2>&1 || die "oracle"

python3 - "$RUN/logits_c.bin" "$RUN/logits_ref.bin" > "$RUN/diff.txt" <<'PY'
"$PY" - "$RUN/logits_c.bin" "$RUN/logits_ref.bin" > "$RUN/diff.txt" <<'PY'
import struct, sys
def L(p):
b = open(p, "rb").read()
Expand Down
Loading