Compare commits
24 Commits
v0.2.1
...
pdevine/fi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3c0d043b79 | ||
|
|
4cfcbc328f | ||
|
|
79292ff3e0 | ||
|
|
8ea500441d | ||
|
|
b50c818623 | ||
|
|
b99e750b62 | ||
|
|
1f50356e8e | ||
|
|
22c81f62ec | ||
|
|
2d1e3c3229 | ||
|
|
4918fae535 | ||
|
|
0aff67877e | ||
|
|
f6f759fc5f | ||
|
|
9544a57ee4 | ||
|
|
b51e3b63ac | ||
|
|
6bbbc50f10 | ||
|
|
9bbddc37a7 | ||
|
|
b44320db13 | ||
|
|
0bacb30007 | ||
|
|
fb6cbc02fb | ||
|
|
326363b3a7 | ||
|
|
ac7a842e55 | ||
|
|
2c3fe1fd97 | ||
|
|
269ed6e6a2 | ||
|
|
784bf88b0d |
7
.github/workflows/release.yaml
vendored
7
.github/workflows/release.yaml
vendored
@@ -147,7 +147,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
$ErrorActionPreference = "Stop"
|
$ErrorActionPreference = "Stop"
|
||||||
write-host "downloading AMD HIP Installer"
|
write-host "downloading AMD HIP Installer"
|
||||||
Invoke-WebRequest -Uri "https://download.amd.com/developer/eula/rocm-hub/AMD-Software-PRO-Edition-23.Q4-WinSvr2022-For-HIP.exe" -OutFile "${env:RUNNER_TEMP}\rocm-install.exe"
|
Invoke-WebRequest -Uri "https://download.amd.com/developer/eula/rocm-hub/AMD-Software-PRO-Edition-24.Q3-WinSvr2022-For-HIP.exe" -OutFile "${env:RUNNER_TEMP}\rocm-install.exe"
|
||||||
write-host "Installing AMD HIP"
|
write-host "Installing AMD HIP"
|
||||||
Start-Process "${env:RUNNER_TEMP}\rocm-install.exe" -ArgumentList '-install' -NoNewWindow -Wait
|
Start-Process "${env:RUNNER_TEMP}\rocm-install.exe" -ArgumentList '-install' -NoNewWindow -Wait
|
||||||
write-host "Completed AMD HIP"
|
write-host "Completed AMD HIP"
|
||||||
@@ -304,11 +304,6 @@ jobs:
|
|||||||
write-host "Installing plugin"
|
write-host "Installing plugin"
|
||||||
& "${env:RUNNER_TEMP}\plugin\*\kmscng.msi" /quiet
|
& "${env:RUNNER_TEMP}\plugin\*\kmscng.msi" /quiet
|
||||||
write-host "plugin installed"
|
write-host "plugin installed"
|
||||||
- name: remove unwanted mingw dll.a files
|
|
||||||
run: |
|
|
||||||
Get-ChildItem -Path "C:\mingw64" -Recurse -Filter "libpthread.dll.a" -File | Remove-Item -Force
|
|
||||||
Get-ChildItem -Path "C:\mingw64" -Recurse -Filter "libwinpthread.dll.a" -File | Remove-Item -Force
|
|
||||||
Get-ChildItem -Path "C:\mingw64" -Recurse -Filter "libstdc++.dll.a" -File | Remove-Item -Force
|
|
||||||
- uses: actions/setup-go@v5
|
- uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version-file: go.mod
|
go-version-file: go.mod
|
||||||
|
|||||||
2
.github/workflows/test.yaml
vendored
2
.github/workflows/test.yaml
vendored
@@ -169,7 +169,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
$ErrorActionPreference = "Stop"
|
$ErrorActionPreference = "Stop"
|
||||||
write-host "downloading AMD HIP Installer"
|
write-host "downloading AMD HIP Installer"
|
||||||
Invoke-WebRequest -Uri "https://download.amd.com/developer/eula/rocm-hub/AMD-Software-PRO-Edition-23.Q4-WinSvr2022-For-HIP.exe" -OutFile "${env:RUNNER_TEMP}\rocm-install.exe"
|
Invoke-WebRequest -Uri "https://download.amd.com/developer/eula/rocm-hub/AMD-Software-PRO-Edition-24.Q3-WinSvr2022-For-HIP.exe" -OutFile "${env:RUNNER_TEMP}\rocm-install.exe"
|
||||||
write-host "Installing AMD HIP"
|
write-host "Installing AMD HIP"
|
||||||
Start-Process "${env:RUNNER_TEMP}\rocm-install.exe" -ArgumentList '-install' -NoNewWindow -Wait
|
Start-Process "${env:RUNNER_TEMP}\rocm-install.exe" -ArgumentList '-install' -NoNewWindow -Wait
|
||||||
write-host "Completed AMD HIP"
|
write-host "Completed AMD HIP"
|
||||||
|
|||||||
@@ -84,6 +84,9 @@ type ChatRequest struct {
|
|||||||
// Model is the model name, as in [GenerateRequest].
|
// Model is the model name, as in [GenerateRequest].
|
||||||
Model string `json:"model"`
|
Model string `json:"model"`
|
||||||
|
|
||||||
|
// Template overrides the model's default prompt template.
|
||||||
|
Template string `json:"template"`
|
||||||
|
|
||||||
// Messages is the messages of the chat - can be used to keep a chat memory.
|
// Messages is the messages of the chat - can be used to keep a chat memory.
|
||||||
Messages []Message `json:"messages"`
|
Messages []Message `json:"messages"`
|
||||||
|
|
||||||
|
|||||||
@@ -947,6 +947,7 @@ func chat(cmd *cobra.Command, opts runOptions) (*api.Message, error) {
|
|||||||
|
|
||||||
req := &api.ChatRequest{
|
req := &api.ChatRequest{
|
||||||
Model: opts.Model,
|
Model: opts.Model,
|
||||||
|
Template: opts.Template,
|
||||||
Messages: opts.Messages,
|
Messages: opts.Messages,
|
||||||
Format: opts.Format,
|
Format: opts.Format,
|
||||||
Options: opts.Options,
|
Options: opts.Options,
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import (
|
|||||||
"github.com/ollama/ollama/envconfig"
|
"github.com/ollama/ollama/envconfig"
|
||||||
"github.com/ollama/ollama/progress"
|
"github.com/ollama/ollama/progress"
|
||||||
"github.com/ollama/ollama/readline"
|
"github.com/ollama/ollama/readline"
|
||||||
|
"github.com/ollama/ollama/template"
|
||||||
"github.com/ollama/ollama/types/errtypes"
|
"github.com/ollama/ollama/types/errtypes"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -205,9 +206,17 @@ func generateInteractive(cmd *cobra.Command, opts runOptions) error {
|
|||||||
fmt.Println("Set system message.")
|
fmt.Println("Set system message.")
|
||||||
sb.Reset()
|
sb.Reset()
|
||||||
case MultilineTemplate:
|
case MultilineTemplate:
|
||||||
opts.Template = sb.String()
|
mTemplate := sb.String()
|
||||||
fmt.Println("Set prompt template.")
|
|
||||||
sb.Reset()
|
sb.Reset()
|
||||||
|
_, err := template.Parse(mTemplate)
|
||||||
|
if err != nil {
|
||||||
|
multiline = MultilineNone
|
||||||
|
scanner.Prompt.UseAlt = false
|
||||||
|
fmt.Println("The template is invalid.")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
opts.Template = mTemplate
|
||||||
|
fmt.Println("Set prompt template.")
|
||||||
}
|
}
|
||||||
|
|
||||||
multiline = MultilineNone
|
multiline = MultilineNone
|
||||||
@@ -369,9 +378,15 @@ func generateInteractive(cmd *cobra.Command, opts runOptions) error {
|
|||||||
fmt.Println("Set system message.")
|
fmt.Println("Set system message.")
|
||||||
sb.Reset()
|
sb.Reset()
|
||||||
} else if args[1] == "template" {
|
} else if args[1] == "template" {
|
||||||
opts.Template = sb.String()
|
mTemplate := sb.String()
|
||||||
fmt.Println("Set prompt template.")
|
|
||||||
sb.Reset()
|
sb.Reset()
|
||||||
|
_, err := template.Parse(mTemplate)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("The template is invalid.")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
opts.Template = mTemplate
|
||||||
|
fmt.Println("Set prompt template.")
|
||||||
}
|
}
|
||||||
|
|
||||||
sb.Reset()
|
sb.Reset()
|
||||||
|
|||||||
@@ -272,4 +272,4 @@ The following server settings may be used to adjust how Ollama handles concurren
|
|||||||
- `OLLAMA_NUM_PARALLEL` - The maximum number of parallel requests each model will process at the same time. The default will auto-select either 4 or 1 based on available memory.
|
- `OLLAMA_NUM_PARALLEL` - The maximum number of parallel requests each model will process at the same time. The default will auto-select either 4 or 1 based on available memory.
|
||||||
- `OLLAMA_MAX_QUEUE` - The maximum number of requests Ollama will queue when busy before rejecting additional requests. The default is 512
|
- `OLLAMA_MAX_QUEUE` - The maximum number of requests Ollama will queue when busy before rejecting additional requests. The default is 512
|
||||||
|
|
||||||
Note: Windows with Radeon GPUs currently default to 1 model maximum due to limitations in ROCm v5.7 for available VRAM reporting. Once ROCm v6 is available, Windows Radeon will follow the defaults above. You may enable concurrent model loads on Radeon on Windows, but ensure you don't load more models than will fit into your GPUs VRAM.
|
Note: Windows with Radeon GPUs currently default to 1 model maximum due to limitations in ROCm v5.7 for available VRAM reporting. Once ROCm v6.2 is available, Windows Radeon will follow the defaults above. You may enable concurrent model loads on Radeon on Windows, but ensure you don't load more models than will fit into your GPUs VRAM.
|
||||||
3
go.mod
3
go.mod
@@ -18,6 +18,7 @@ require (
|
|||||||
require (
|
require (
|
||||||
github.com/agnivade/levenshtein v1.1.1
|
github.com/agnivade/levenshtein v1.1.1
|
||||||
github.com/d4l3k/go-bfloat16 v0.0.0-20211005043715-690c3bdd05f1
|
github.com/d4l3k/go-bfloat16 v0.0.0-20211005043715-690c3bdd05f1
|
||||||
|
github.com/google/go-cmp v0.6.0
|
||||||
github.com/mattn/go-runewidth v0.0.14
|
github.com/mattn/go-runewidth v0.0.14
|
||||||
github.com/nlpodyssey/gopickle v0.3.0
|
github.com/nlpodyssey/gopickle v0.3.0
|
||||||
github.com/pdevine/tensor v0.0.0-20240510204454-f88f4562727c
|
github.com/pdevine/tensor v0.0.0-20240510204454-f88f4562727c
|
||||||
@@ -71,7 +72,7 @@ require (
|
|||||||
golang.org/x/net v0.25.0 // indirect
|
golang.org/x/net v0.25.0 // indirect
|
||||||
golang.org/x/sys v0.20.0
|
golang.org/x/sys v0.20.0
|
||||||
golang.org/x/term v0.20.0
|
golang.org/x/term v0.20.0
|
||||||
golang.org/x/text v0.15.0 // indirect
|
golang.org/x/text v0.15.0
|
||||||
google.golang.org/protobuf v1.34.1
|
google.golang.org/protobuf v1.34.1
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -49,9 +49,17 @@ func rocmGetVisibleDevicesEnv(gpuInfo []GpuInfo) (string, string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func commonAMDValidateLibDir() (string, error) {
|
func commonAMDValidateLibDir() (string, error) {
|
||||||
// We try to favor system paths first, so that we can wire up the subprocess to use
|
// Favor our bundled version
|
||||||
// the system version. Only use our bundled version if the system version doesn't work
|
|
||||||
// This gives users a more recovery options if versions have subtle problems at runtime
|
// Installer payload location if we're running the installed binary
|
||||||
|
exe, err := os.Executable()
|
||||||
|
if err == nil {
|
||||||
|
rocmTargetDir := filepath.Join(filepath.Dir(exe), "rocm")
|
||||||
|
if rocmLibUsable(rocmTargetDir) {
|
||||||
|
slog.Debug("detected ROCM next to ollama executable " + rocmTargetDir)
|
||||||
|
return rocmTargetDir, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Prefer explicit HIP env var
|
// Prefer explicit HIP env var
|
||||||
hipPath := os.Getenv("HIP_PATH")
|
hipPath := os.Getenv("HIP_PATH")
|
||||||
@@ -87,14 +95,5 @@ func commonAMDValidateLibDir() (string, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Installer payload location if we're running the installed binary
|
|
||||||
exe, err := os.Executable()
|
|
||||||
if err == nil {
|
|
||||||
rocmTargetDir := filepath.Join(filepath.Dir(exe), "rocm")
|
|
||||||
if rocmLibUsable(rocmTargetDir) {
|
|
||||||
slog.Debug("detected ROCM next to ollama executable " + rocmTargetDir)
|
|
||||||
return rocmTargetDir, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "", fmt.Errorf("no suitable rocm found, falling back to CPU")
|
return "", fmt.Errorf("no suitable rocm found, falling back to CPU")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,9 +84,8 @@ func (hl *HipLib) AMDDriverVersion() (driverMajor, driverMinor int, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
slog.Debug("hipDriverGetVersion", "version", version)
|
slog.Debug("hipDriverGetVersion", "version", version)
|
||||||
// TODO - this isn't actually right, but the docs claim hipDriverGetVersion isn't accurate anyway...
|
driverMajor = version / 10000000
|
||||||
driverMajor = version / 1000
|
driverMinor = (version - (driverMajor * 10000000)) / 100000
|
||||||
driverMinor = (version - (driverMajor * 1000)) / 10
|
|
||||||
|
|
||||||
return driverMajor, driverMinor, nil
|
return driverMajor, driverMinor, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ const (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
// Used to validate if the given ROCm lib is usable
|
// Used to validate if the given ROCm lib is usable
|
||||||
ROCmLibGlobs = []string{"hipblas.dll", "rocblas"} // TODO - probably include more coverage of files here...
|
ROCmLibGlobs = []string{"hipblas.dll", "rocblas"} // This is not sufficient to discern v5 vs v6
|
||||||
RocmStandardLocations = []string{"C:\\Program Files\\AMD\\ROCm\\5.7\\bin"} // TODO glob?
|
RocmStandardLocations = []string{"C:\\Program Files\\AMD\\ROCm\\6.1\\bin"} // TODO glob?
|
||||||
)
|
)
|
||||||
|
|
||||||
func AMDGetGPUInfo() []RocmGPUInfo {
|
func AMDGetGPUInfo() []RocmGPUInfo {
|
||||||
@@ -35,12 +35,11 @@ func AMDGetGPUInfo() []RocmGPUInfo {
|
|||||||
}
|
}
|
||||||
defer hl.Release()
|
defer hl.Release()
|
||||||
|
|
||||||
// TODO - this reports incorrect version information, so omitting for now
|
driverMajor, driverMinor, err := hl.AMDDriverVersion()
|
||||||
// driverMajor, driverMinor, err := hl.AMDDriverVersion()
|
if err != nil {
|
||||||
// if err != nil {
|
// For now this is benign, but we may eventually need to fail compatibility checks
|
||||||
// // For now this is benign, but we may eventually need to fail compatibility checks
|
slog.Debug("error looking up amd driver version", "error", err)
|
||||||
// slog.Debug("error looking up amd driver version", "error", err)
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
// Note: the HIP library automatically handles subsetting to any HIP_VISIBLE_DEVICES the user specified
|
// Note: the HIP library automatically handles subsetting to any HIP_VISIBLE_DEVICES the user specified
|
||||||
count := hl.HipGetDeviceCount()
|
count := hl.HipGetDeviceCount()
|
||||||
@@ -132,10 +131,8 @@ func AMDGetGPUInfo() []RocmGPUInfo {
|
|||||||
MinimumMemory: rocmMinimumMemory,
|
MinimumMemory: rocmMinimumMemory,
|
||||||
Name: name,
|
Name: name,
|
||||||
Compute: gfx,
|
Compute: gfx,
|
||||||
|
DriverMajor: driverMajor,
|
||||||
// TODO - this information isn't accurate on windows, so don't report it until we find the right way to retrieve
|
DriverMinor: driverMinor,
|
||||||
// DriverMajor: driverMajor,
|
|
||||||
// DriverMinor: driverMinor,
|
|
||||||
},
|
},
|
||||||
index: i,
|
index: i,
|
||||||
}
|
}
|
||||||
|
|||||||
27
gpu/gpu.go
27
gpu/gpu.go
@@ -274,6 +274,28 @@ func GetGPUInfo() GpuInfoList {
|
|||||||
gpuInfo.DriverMajor = driverMajor
|
gpuInfo.DriverMajor = driverMajor
|
||||||
gpuInfo.DriverMinor = driverMinor
|
gpuInfo.DriverMinor = driverMinor
|
||||||
|
|
||||||
|
// query the management library as well so we can record any skew between the two
|
||||||
|
// which represents overhead on the GPU we must set aside on subsequent updates
|
||||||
|
if cHandles.nvml != nil {
|
||||||
|
C.nvml_get_free(*cHandles.nvml, C.int(gpuInfo.index), &memInfo.free, &memInfo.total, &memInfo.used)
|
||||||
|
if memInfo.err != nil {
|
||||||
|
slog.Warn("error looking up nvidia GPU memory", "error", C.GoString(memInfo.err))
|
||||||
|
C.free(unsafe.Pointer(memInfo.err))
|
||||||
|
} else {
|
||||||
|
if memInfo.free != 0 && uint64(memInfo.free) > gpuInfo.FreeMemory {
|
||||||
|
gpuInfo.OSOverhead = uint64(memInfo.free) - gpuInfo.FreeMemory
|
||||||
|
slog.Info("detected OS VRAM overhead",
|
||||||
|
"id", gpuInfo.ID,
|
||||||
|
"library", gpuInfo.Library,
|
||||||
|
"compute", gpuInfo.Compute,
|
||||||
|
"driver", fmt.Sprintf("%d.%d", gpuInfo.DriverMajor, gpuInfo.DriverMinor),
|
||||||
|
"name", gpuInfo.Name,
|
||||||
|
"overhead", format.HumanBytes2(gpuInfo.OSOverhead),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO potentially sort on our own algorithm instead of what the underlying GPU library does...
|
// TODO potentially sort on our own algorithm instead of what the underlying GPU library does...
|
||||||
cudaGPUs = append(cudaGPUs, gpuInfo)
|
cudaGPUs = append(cudaGPUs, gpuInfo)
|
||||||
}
|
}
|
||||||
@@ -374,9 +396,14 @@ func GetGPUInfo() GpuInfoList {
|
|||||||
slog.Warn("error looking up nvidia GPU memory")
|
slog.Warn("error looking up nvidia GPU memory")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if cHandles.nvml != nil && gpu.OSOverhead > 0 {
|
||||||
|
// When using the management library update based on recorded overhead
|
||||||
|
memInfo.free -= C.uint64_t(gpu.OSOverhead)
|
||||||
|
}
|
||||||
slog.Debug("updating cuda memory data",
|
slog.Debug("updating cuda memory data",
|
||||||
"gpu", gpu.ID,
|
"gpu", gpu.ID,
|
||||||
"name", gpu.Name,
|
"name", gpu.Name,
|
||||||
|
"overhead", format.HumanBytes2(gpu.OSOverhead),
|
||||||
slog.Group(
|
slog.Group(
|
||||||
"before",
|
"before",
|
||||||
"total", format.HumanBytes2(gpu.TotalMemory),
|
"total", format.HumanBytes2(gpu.TotalMemory),
|
||||||
|
|||||||
@@ -52,7 +52,8 @@ type CPUInfo struct {
|
|||||||
|
|
||||||
type CudaGPUInfo struct {
|
type CudaGPUInfo struct {
|
||||||
GpuInfo
|
GpuInfo
|
||||||
index int //nolint:unused,nolintlint
|
OSOverhead uint64 // Memory overhead between the driver library and management library
|
||||||
|
index int //nolint:unused,nolintlint
|
||||||
}
|
}
|
||||||
type CudaGPUInfoList []CudaGPUInfo
|
type CudaGPUInfoList []CudaGPUInfo
|
||||||
|
|
||||||
|
|||||||
@@ -254,7 +254,7 @@ if [ -z "${OLLAMA_SKIP_ROCM_GENERATE}" -a -d "${ROCM_PATH}" ]; then
|
|||||||
ROCM_VARIANT=_v$(ls ${ROCM_PATH}/lib/librocblas.so.*.*.????? | cut -f5 -d. || true)
|
ROCM_VARIANT=_v$(ls ${ROCM_PATH}/lib/librocblas.so.*.*.????? | cut -f5 -d. || true)
|
||||||
fi
|
fi
|
||||||
init_vars
|
init_vars
|
||||||
CMAKE_DEFS="${COMMON_CMAKE_DEFS} ${CMAKE_DEFS} -DGGML_HIPBLAS=on -DCMAKE_C_COMPILER=$ROCM_PATH/llvm/bin/clang -DCMAKE_CXX_COMPILER=$ROCM_PATH/llvm/bin/clang++ -DAMDGPU_TARGETS=$(amdGPUs) -DGPU_TARGETS=$(amdGPUs)"
|
CMAKE_DEFS="${COMMON_CMAKE_DEFS} ${CMAKE_DEFS} -DGGML_HIPBLAS=on -DLLAMA_CUDA_NO_PEER_COPY=on -DCMAKE_C_COMPILER=$ROCM_PATH/llvm/bin/clang -DCMAKE_CXX_COMPILER=$ROCM_PATH/llvm/bin/clang++ -DAMDGPU_TARGETS=$(amdGPUs) -DGPU_TARGETS=$(amdGPUs)"
|
||||||
# Users building from source can tune the exact flags we pass to cmake for configuring llama.cpp
|
# Users building from source can tune the exact flags we pass to cmake for configuring llama.cpp
|
||||||
if [ -n "${OLLAMA_CUSTOM_ROCM_DEFS}" ]; then
|
if [ -n "${OLLAMA_CUSTOM_ROCM_DEFS}" ]; then
|
||||||
echo "OLLAMA_CUSTOM_ROCM_DEFS=\"${OLLAMA_CUSTOM_ROCM_DEFS}\""
|
echo "OLLAMA_CUSTOM_ROCM_DEFS=\"${OLLAMA_CUSTOM_ROCM_DEFS}\""
|
||||||
|
|||||||
@@ -6,18 +6,9 @@ function amdGPUs {
|
|||||||
if ($env:AMDGPU_TARGETS) {
|
if ($env:AMDGPU_TARGETS) {
|
||||||
return $env:AMDGPU_TARGETS
|
return $env:AMDGPU_TARGETS
|
||||||
}
|
}
|
||||||
# TODO - load from some common data file for linux + windows build consistency
|
# Current supported rocblas list from ROCm v6.1.2 on windows
|
||||||
$GPU_LIST = @(
|
$GPU_LIST = @(
|
||||||
"gfx900"
|
|
||||||
"gfx906:xnack-"
|
"gfx906:xnack-"
|
||||||
"gfx908:xnack-"
|
|
||||||
"gfx90a:xnack+"
|
|
||||||
"gfx90a:xnack-"
|
|
||||||
"gfx940"
|
|
||||||
"gfx941"
|
|
||||||
"gfx942"
|
|
||||||
"gfx1010"
|
|
||||||
"gfx1012"
|
|
||||||
"gfx1030"
|
"gfx1030"
|
||||||
"gfx1100"
|
"gfx1100"
|
||||||
"gfx1101"
|
"gfx1101"
|
||||||
@@ -366,6 +357,7 @@ function build_rocm() {
|
|||||||
"-DCMAKE_C_COMPILER=clang.exe",
|
"-DCMAKE_C_COMPILER=clang.exe",
|
||||||
"-DCMAKE_CXX_COMPILER=clang++.exe",
|
"-DCMAKE_CXX_COMPILER=clang++.exe",
|
||||||
"-DGGML_HIPBLAS=on",
|
"-DGGML_HIPBLAS=on",
|
||||||
|
"-DLLAMA_CUDA_NO_PEER_COPY=on",
|
||||||
"-DHIP_PLATFORM=amd",
|
"-DHIP_PLATFORM=amd",
|
||||||
"-DGGML_AVX=on",
|
"-DGGML_AVX=on",
|
||||||
"-DGGML_AVX2=off",
|
"-DGGML_AVX2=off",
|
||||||
@@ -394,7 +386,6 @@ function build_rocm() {
|
|||||||
sign
|
sign
|
||||||
install
|
install
|
||||||
|
|
||||||
# Assumes v5.7, may need adjustments for v6
|
|
||||||
rm -ea 0 -recurse -force -path "${script:SRC_DIR}\dist\windows-${script:ARCH}\rocm\"
|
rm -ea 0 -recurse -force -path "${script:SRC_DIR}\dist\windows-${script:ARCH}\rocm\"
|
||||||
md "${script:SRC_DIR}\dist\windows-${script:ARCH}\rocm\rocblas\library\" -ea 0 > $null
|
md "${script:SRC_DIR}\dist\windows-${script:ARCH}\rocm\rocblas\library\" -ea 0 > $null
|
||||||
cp "${env:HIP_PATH}\bin\hipblas.dll" "${script:SRC_DIR}\dist\windows-${script:ARCH}\rocm\"
|
cp "${env:HIP_PATH}\bin\hipblas.dll" "${script:SRC_DIR}\dist\windows-${script:ARCH}\rocm\"
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ package llm
|
|||||||
// #cgo LDFLAGS: -lllama -lggml -lstdc++ -lpthread
|
// #cgo LDFLAGS: -lllama -lggml -lstdc++ -lpthread
|
||||||
// #cgo darwin,arm64 LDFLAGS: -L${SRCDIR}/build/darwin/arm64_static -L${SRCDIR}/build/darwin/arm64_static/src -L${SRCDIR}/build/darwin/arm64_static/ggml/src -framework Accelerate -framework Metal
|
// #cgo darwin,arm64 LDFLAGS: -L${SRCDIR}/build/darwin/arm64_static -L${SRCDIR}/build/darwin/arm64_static/src -L${SRCDIR}/build/darwin/arm64_static/ggml/src -framework Accelerate -framework Metal
|
||||||
// #cgo darwin,amd64 LDFLAGS: -L${SRCDIR}/build/darwin/x86_64_static -L${SRCDIR}/build/darwin/x86_64_static/src -L${SRCDIR}/build/darwin/x86_64_static/ggml/src
|
// #cgo darwin,amd64 LDFLAGS: -L${SRCDIR}/build/darwin/x86_64_static -L${SRCDIR}/build/darwin/x86_64_static/src -L${SRCDIR}/build/darwin/x86_64_static/ggml/src
|
||||||
// #cgo windows,amd64 LDFLAGS: -L${SRCDIR}/build/windows/amd64_static -L${SRCDIR}/build/windows/amd64_static/src -L${SRCDIR}/build/windows/amd64_static/ggml/src
|
// #cgo windows,amd64 LDFLAGS: -static-libstdc++ -static-libgcc -static -L${SRCDIR}/build/windows/amd64_static -L${SRCDIR}/build/windows/amd64_static/src -L${SRCDIR}/build/windows/amd64_static/ggml/src
|
||||||
// #cgo windows,arm64 LDFLAGS: -L${SRCDIR}/build/windows/arm64_static -L${SRCDIR}/build/windows/arm64_static/src -L${SRCDIR}/build/windows/arm64_static/ggml/src
|
// #cgo windows,arm64 LDFLAGS: -static-libstdc++ -static-libgcc -static -L${SRCDIR}/build/windows/arm64_static -L${SRCDIR}/build/windows/arm64_static/src -L${SRCDIR}/build/windows/arm64_static/ggml/src
|
||||||
// #cgo linux,amd64 LDFLAGS: -L${SRCDIR}/build/linux/x86_64_static -L${SRCDIR}/build/linux/x86_64_static/src -L${SRCDIR}/build/linux/x86_64_static/ggml/src
|
// #cgo linux,amd64 LDFLAGS: -L${SRCDIR}/build/linux/x86_64_static -L${SRCDIR}/build/linux/x86_64_static/src -L${SRCDIR}/build/linux/x86_64_static/ggml/src
|
||||||
// #cgo linux,arm64 LDFLAGS: -L${SRCDIR}/build/linux/arm64_static -L${SRCDIR}/build/linux/arm64_static/src -L${SRCDIR}/build/linux/arm64_static/ggml/src
|
// #cgo linux,arm64 LDFLAGS: -L${SRCDIR}/build/linux/arm64_static -L${SRCDIR}/build/linux/arm64_static/src -L${SRCDIR}/build/linux/arm64_static/ggml/src
|
||||||
// #include <stdlib.h>
|
// #include <stdlib.h>
|
||||||
|
|||||||
@@ -254,10 +254,6 @@ func NewLlamaServer(gpus gpu.GpuInfoList, model string, ggml *GGML, adapters, pr
|
|||||||
params = append(params, "--tensor-split", estimate.TensorSplit)
|
params = append(params, "--tensor-split", estimate.TensorSplit)
|
||||||
}
|
}
|
||||||
|
|
||||||
if estimate.TensorSplit != "" {
|
|
||||||
params = append(params, "--tensor-split", estimate.TensorSplit)
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := range len(servers) {
|
for i := range len(servers) {
|
||||||
dir := availableServers[servers[i]]
|
dir := availableServers[servers[i]]
|
||||||
if dir == "" {
|
if dir == "" {
|
||||||
@@ -679,7 +675,7 @@ type CompletionRequest struct {
|
|||||||
Prompt string
|
Prompt string
|
||||||
Format string
|
Format string
|
||||||
Images []ImageData
|
Images []ImageData
|
||||||
Options api.Options
|
Options *api.Options
|
||||||
}
|
}
|
||||||
|
|
||||||
type CompletionResponse struct {
|
type CompletionResponse struct {
|
||||||
|
|||||||
@@ -338,12 +338,16 @@ func fromCompleteRequest(r CompletionRequest) (api.GenerateRequest, error) {
|
|||||||
switch stop := r.Stop.(type) {
|
switch stop := r.Stop.(type) {
|
||||||
case string:
|
case string:
|
||||||
options["stop"] = []string{stop}
|
options["stop"] = []string{stop}
|
||||||
case []string:
|
case []any:
|
||||||
options["stop"] = stop
|
var stops []string
|
||||||
default:
|
for _, s := range stop {
|
||||||
if r.Stop != nil {
|
if str, ok := s.(string); ok {
|
||||||
return api.GenerateRequest{}, fmt.Errorf("invalid type for 'stop' field: %T", r.Stop)
|
stops = append(stops, str)
|
||||||
|
} else {
|
||||||
|
return api.GenerateRequest{}, fmt.Errorf("invalid type for 'stop' field: %T", s)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
options["stop"] = stops
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.MaxTokens != nil {
|
if r.MaxTokens != nil {
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package openai
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
@@ -16,7 +15,133 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMiddleware(t *testing.T) {
|
func TestMiddlewareRequests(t *testing.T) {
|
||||||
|
type testCase struct {
|
||||||
|
Name string
|
||||||
|
Method string
|
||||||
|
Path string
|
||||||
|
Handler func() gin.HandlerFunc
|
||||||
|
Setup func(t *testing.T, req *http.Request)
|
||||||
|
Expected func(t *testing.T, req *http.Request)
|
||||||
|
}
|
||||||
|
|
||||||
|
var capturedRequest *http.Request
|
||||||
|
|
||||||
|
captureRequestMiddleware := func() gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
bodyBytes, _ := io.ReadAll(c.Request.Body)
|
||||||
|
c.Request.Body = io.NopCloser(bytes.NewReader(bodyBytes))
|
||||||
|
capturedRequest = c.Request
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := []testCase{
|
||||||
|
{
|
||||||
|
Name: "chat handler",
|
||||||
|
Method: http.MethodPost,
|
||||||
|
Path: "/api/chat",
|
||||||
|
Handler: ChatMiddleware,
|
||||||
|
Setup: func(t *testing.T, req *http.Request) {
|
||||||
|
body := ChatCompletionRequest{
|
||||||
|
Model: "test-model",
|
||||||
|
Messages: []Message{{Role: "user", Content: "Hello"}},
|
||||||
|
}
|
||||||
|
|
||||||
|
bodyBytes, _ := json.Marshal(body)
|
||||||
|
|
||||||
|
req.Body = io.NopCloser(bytes.NewReader(bodyBytes))
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
},
|
||||||
|
Expected: func(t *testing.T, req *http.Request) {
|
||||||
|
var chatReq api.ChatRequest
|
||||||
|
if err := json.NewDecoder(req.Body).Decode(&chatReq); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if chatReq.Messages[0].Role != "user" {
|
||||||
|
t.Fatalf("expected 'user', got %s", chatReq.Messages[0].Role)
|
||||||
|
}
|
||||||
|
|
||||||
|
if chatReq.Messages[0].Content != "Hello" {
|
||||||
|
t.Fatalf("expected 'Hello', got %s", chatReq.Messages[0].Content)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "completions handler",
|
||||||
|
Method: http.MethodPost,
|
||||||
|
Path: "/api/generate",
|
||||||
|
Handler: CompletionsMiddleware,
|
||||||
|
Setup: func(t *testing.T, req *http.Request) {
|
||||||
|
temp := float32(0.8)
|
||||||
|
body := CompletionRequest{
|
||||||
|
Model: "test-model",
|
||||||
|
Prompt: "Hello",
|
||||||
|
Temperature: &temp,
|
||||||
|
Stop: []string{"\n", "stop"},
|
||||||
|
}
|
||||||
|
|
||||||
|
bodyBytes, _ := json.Marshal(body)
|
||||||
|
|
||||||
|
req.Body = io.NopCloser(bytes.NewReader(bodyBytes))
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
},
|
||||||
|
Expected: func(t *testing.T, req *http.Request) {
|
||||||
|
var genReq api.GenerateRequest
|
||||||
|
if err := json.NewDecoder(req.Body).Decode(&genReq); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if genReq.Prompt != "Hello" {
|
||||||
|
t.Fatalf("expected 'Hello', got %s", genReq.Prompt)
|
||||||
|
}
|
||||||
|
|
||||||
|
if genReq.Options["temperature"] != 1.6 {
|
||||||
|
t.Fatalf("expected 1.6, got %f", genReq.Options["temperature"])
|
||||||
|
}
|
||||||
|
|
||||||
|
stopTokens, ok := genReq.Options["stop"].([]any)
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("expected stop tokens to be a list")
|
||||||
|
}
|
||||||
|
|
||||||
|
if stopTokens[0] != "\n" || stopTokens[1] != "stop" {
|
||||||
|
t.Fatalf("expected ['\\n', 'stop'], got %v", stopTokens)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
gin.SetMode(gin.TestMode)
|
||||||
|
router := gin.New()
|
||||||
|
|
||||||
|
endpoint := func(c *gin.Context) {
|
||||||
|
c.Status(http.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.Name, func(t *testing.T) {
|
||||||
|
router = gin.New()
|
||||||
|
router.Use(captureRequestMiddleware())
|
||||||
|
router.Use(tc.Handler())
|
||||||
|
router.Handle(tc.Method, tc.Path, endpoint)
|
||||||
|
req, _ := http.NewRequest(tc.Method, tc.Path, nil)
|
||||||
|
|
||||||
|
if tc.Setup != nil {
|
||||||
|
tc.Setup(t, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := httptest.NewRecorder()
|
||||||
|
router.ServeHTTP(resp, req)
|
||||||
|
|
||||||
|
tc.Expected(t, capturedRequest)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMiddlewareResponses(t *testing.T) {
|
||||||
type testCase struct {
|
type testCase struct {
|
||||||
Name string
|
Name string
|
||||||
Method string
|
Method string
|
||||||
@@ -30,159 +155,7 @@ func TestMiddleware(t *testing.T) {
|
|||||||
|
|
||||||
testCases := []testCase{
|
testCases := []testCase{
|
||||||
{
|
{
|
||||||
Name: "chat handler",
|
Name: "completions handler error forwarding",
|
||||||
Method: http.MethodPost,
|
|
||||||
Path: "/api/chat",
|
|
||||||
TestPath: "/api/chat",
|
|
||||||
Handler: ChatMiddleware,
|
|
||||||
Endpoint: func(c *gin.Context) {
|
|
||||||
var chatReq api.ChatRequest
|
|
||||||
if err := c.ShouldBindJSON(&chatReq); err != nil {
|
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
userMessage := chatReq.Messages[0].Content
|
|
||||||
var assistantMessage string
|
|
||||||
|
|
||||||
switch userMessage {
|
|
||||||
case "Hello":
|
|
||||||
assistantMessage = "Hello!"
|
|
||||||
default:
|
|
||||||
assistantMessage = "I'm not sure how to respond to that."
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, api.ChatResponse{
|
|
||||||
Message: api.Message{
|
|
||||||
Role: "assistant",
|
|
||||||
Content: assistantMessage,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
},
|
|
||||||
Setup: func(t *testing.T, req *http.Request) {
|
|
||||||
body := ChatCompletionRequest{
|
|
||||||
Model: "test-model",
|
|
||||||
Messages: []Message{{Role: "user", Content: "Hello"}},
|
|
||||||
}
|
|
||||||
|
|
||||||
bodyBytes, _ := json.Marshal(body)
|
|
||||||
|
|
||||||
req.Body = io.NopCloser(bytes.NewReader(bodyBytes))
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
|
||||||
},
|
|
||||||
Expected: func(t *testing.T, resp *httptest.ResponseRecorder) {
|
|
||||||
assert.Equal(t, http.StatusOK, resp.Code)
|
|
||||||
|
|
||||||
var chatResp ChatCompletion
|
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&chatResp); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if chatResp.Object != "chat.completion" {
|
|
||||||
t.Fatalf("expected chat.completion, got %s", chatResp.Object)
|
|
||||||
}
|
|
||||||
|
|
||||||
if chatResp.Choices[0].Message.Content != "Hello!" {
|
|
||||||
t.Fatalf("expected Hello!, got %s", chatResp.Choices[0].Message.Content)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "completions handler",
|
|
||||||
Method: http.MethodPost,
|
|
||||||
Path: "/api/generate",
|
|
||||||
TestPath: "/api/generate",
|
|
||||||
Handler: CompletionsMiddleware,
|
|
||||||
Endpoint: func(c *gin.Context) {
|
|
||||||
c.JSON(http.StatusOK, api.GenerateResponse{
|
|
||||||
Response: "Hello!",
|
|
||||||
})
|
|
||||||
},
|
|
||||||
Setup: func(t *testing.T, req *http.Request) {
|
|
||||||
body := CompletionRequest{
|
|
||||||
Model: "test-model",
|
|
||||||
Prompt: "Hello",
|
|
||||||
}
|
|
||||||
|
|
||||||
bodyBytes, _ := json.Marshal(body)
|
|
||||||
|
|
||||||
req.Body = io.NopCloser(bytes.NewReader(bodyBytes))
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
|
||||||
},
|
|
||||||
Expected: func(t *testing.T, resp *httptest.ResponseRecorder) {
|
|
||||||
assert.Equal(t, http.StatusOK, resp.Code)
|
|
||||||
var completionResp Completion
|
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&completionResp); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if completionResp.Object != "text_completion" {
|
|
||||||
t.Fatalf("expected text_completion, got %s", completionResp.Object)
|
|
||||||
}
|
|
||||||
|
|
||||||
if completionResp.Choices[0].Text != "Hello!" {
|
|
||||||
t.Fatalf("expected Hello!, got %s", completionResp.Choices[0].Text)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "completions handler with params",
|
|
||||||
Method: http.MethodPost,
|
|
||||||
Path: "/api/generate",
|
|
||||||
TestPath: "/api/generate",
|
|
||||||
Handler: CompletionsMiddleware,
|
|
||||||
Endpoint: func(c *gin.Context) {
|
|
||||||
var generateReq api.GenerateRequest
|
|
||||||
if err := c.ShouldBindJSON(&generateReq); err != nil {
|
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
temperature := generateReq.Options["temperature"].(float64)
|
|
||||||
var assistantMessage string
|
|
||||||
|
|
||||||
switch temperature {
|
|
||||||
case 1.6:
|
|
||||||
assistantMessage = "Received temperature of 1.6"
|
|
||||||
default:
|
|
||||||
assistantMessage = fmt.Sprintf("Received temperature of %f", temperature)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, api.GenerateResponse{
|
|
||||||
Response: assistantMessage,
|
|
||||||
})
|
|
||||||
},
|
|
||||||
Setup: func(t *testing.T, req *http.Request) {
|
|
||||||
temp := float32(0.8)
|
|
||||||
body := CompletionRequest{
|
|
||||||
Model: "test-model",
|
|
||||||
Prompt: "Hello",
|
|
||||||
Temperature: &temp,
|
|
||||||
}
|
|
||||||
|
|
||||||
bodyBytes, _ := json.Marshal(body)
|
|
||||||
|
|
||||||
req.Body = io.NopCloser(bytes.NewReader(bodyBytes))
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
|
||||||
},
|
|
||||||
Expected: func(t *testing.T, resp *httptest.ResponseRecorder) {
|
|
||||||
assert.Equal(t, http.StatusOK, resp.Code)
|
|
||||||
var completionResp Completion
|
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&completionResp); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if completionResp.Object != "text_completion" {
|
|
||||||
t.Fatalf("expected text_completion, got %s", completionResp.Object)
|
|
||||||
}
|
|
||||||
|
|
||||||
if completionResp.Choices[0].Text != "Received temperature of 1.6" {
|
|
||||||
t.Fatalf("expected Received temperature of 1.6, got %s", completionResp.Choices[0].Text)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "completions handler with error",
|
|
||||||
Method: http.MethodPost,
|
Method: http.MethodPost,
|
||||||
Path: "/api/generate",
|
Path: "/api/generate",
|
||||||
TestPath: "/api/generate",
|
TestPath: "/api/generate",
|
||||||
|
|||||||
@@ -107,9 +107,12 @@ function gatherDependencies() {
|
|||||||
|
|
||||||
# TODO - this varies based on host build system and MSVC version - drive from dumpbin output
|
# TODO - this varies based on host build system and MSVC version - drive from dumpbin output
|
||||||
# currently works for Win11 + MSVC 2019 + Cuda V11
|
# currently works for Win11 + MSVC 2019 + Cuda V11
|
||||||
cp "${env:VCToolsRedistDir}\x64\Microsoft.VC*.CRT\msvcp140.dll" "${script:DEPS_DIR}\ollama_runners\"
|
cp "${env:VCToolsRedistDir}\x64\Microsoft.VC*.CRT\msvcp140*.dll" "${script:DEPS_DIR}\ollama_runners\"
|
||||||
cp "${env:VCToolsRedistDir}\x64\Microsoft.VC*.CRT\vcruntime140.dll" "${script:DEPS_DIR}\ollama_runners\"
|
cp "${env:VCToolsRedistDir}\x64\Microsoft.VC*.CRT\vcruntime140.dll" "${script:DEPS_DIR}\ollama_runners\"
|
||||||
cp "${env:VCToolsRedistDir}\x64\Microsoft.VC*.CRT\vcruntime140_1.dll" "${script:DEPS_DIR}\ollama_runners\"
|
cp "${env:VCToolsRedistDir}\x64\Microsoft.VC*.CRT\vcruntime140_1.dll" "${script:DEPS_DIR}\ollama_runners\"
|
||||||
|
foreach ($part in $("runtime", "stdio", "filesystem", "math", "convert", "heap", "string", "time", "locale", "environment")) {
|
||||||
|
cp "$env:VCToolsRedistDir\..\..\..\Tools\Llvm\x64\bin\api-ms-win-crt-${part}*.dll" "${script:DEPS_DIR}\ollama_runners\"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
cp "${script:SRC_DIR}\app\ollama_welcome.ps1" "${script:SRC_DIR}\dist\"
|
cp "${script:SRC_DIR}\app\ollama_welcome.ps1" "${script:SRC_DIR}\dist\"
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ import (
|
|||||||
"github.com/ollama/ollama/version"
|
"github.com/ollama/ollama/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var errCapabilityCompletion = errors.New("completion")
|
||||||
|
|
||||||
type Capability string
|
type Capability string
|
||||||
|
|
||||||
const CapabilityCompletion = Capability("completion")
|
const CapabilityCompletion = Capability("completion")
|
||||||
@@ -62,7 +64,10 @@ type Model struct {
|
|||||||
Template *template.Template
|
Template *template.Template
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Model) Has(caps ...Capability) bool {
|
// CheckCapabilities checks if the model has the specified capabilities returning an error describing
|
||||||
|
// any missing or unknown capabilities
|
||||||
|
func (m *Model) CheckCapabilities(caps ...Capability) error {
|
||||||
|
var errs []error
|
||||||
for _, cap := range caps {
|
for _, cap := range caps {
|
||||||
switch cap {
|
switch cap {
|
||||||
case CapabilityCompletion:
|
case CapabilityCompletion:
|
||||||
@@ -81,15 +86,19 @@ func (m *Model) Has(caps ...Capability) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := ggml.KV()[fmt.Sprintf("%s.pooling_type", ggml.KV().Architecture())]; ok {
|
if _, ok := ggml.KV()[fmt.Sprintf("%s.pooling_type", ggml.KV().Architecture())]; ok {
|
||||||
return false
|
errs = append(errs, errCapabilityCompletion)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
slog.Error("unknown capability", "capability", cap)
|
slog.Error("unknown capability", "capability", cap)
|
||||||
return false
|
return fmt.Errorf("unknown capability: %s", cap)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
if err := errors.Join(errs...); err != nil {
|
||||||
|
return fmt.Errorf("missing capabilities: %w", errors.Join(errs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Model) String() string {
|
func (m *Model) String() string {
|
||||||
|
|||||||
250
server/prompt.go
250
server/prompt.go
@@ -1,217 +1,83 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"bytes"
|
||||||
|
"context"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"strings"
|
"slices"
|
||||||
|
|
||||||
"text/template/parse"
|
|
||||||
|
|
||||||
"github.com/ollama/ollama/api"
|
"github.com/ollama/ollama/api"
|
||||||
|
"github.com/ollama/ollama/llm"
|
||||||
"github.com/ollama/ollama/template"
|
"github.com/ollama/ollama/template"
|
||||||
)
|
)
|
||||||
|
|
||||||
// isResponseNode checks if the node contains .Response
|
type tokenizeFunc func(context.Context, string) ([]int, error)
|
||||||
func isResponseNode(node *parse.ActionNode) bool {
|
|
||||||
for _, cmd := range node.Pipe.Cmds {
|
// chatPrompt accepts a list of messages and returns the prompt and images that should be used for the next chat turn.
|
||||||
for _, arg := range cmd.Args {
|
// chatPrompt truncates any messages that exceed the context window of the model, making sure to always include 1) the
|
||||||
if fieldNode, ok := arg.(*parse.FieldNode); ok && len(fieldNode.Ident) > 0 {
|
// latest message and 2) system messages
|
||||||
if fieldNode.Ident[0] == "Response" {
|
func chatPrompt(ctx context.Context, m *Model, tokenize tokenizeFunc, opts *api.Options, msgs []api.Message) (prompt string, images []llm.ImageData, _ error) {
|
||||||
return true
|
// pull out any system messages which should always be included in the prompt
|
||||||
}
|
var system []api.Message
|
||||||
}
|
msgs = slices.DeleteFunc(msgs, func(m api.Message) bool {
|
||||||
|
if m.Role == "system" {
|
||||||
|
system = append(system, m)
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// formatTemplateForResponse formats the template AST to:
|
return false
|
||||||
// 1. remove all nodes after the first .Response (if generate=true)
|
})
|
||||||
// 2. add a .Response node to the end if it doesn't exist
|
|
||||||
// TODO(jmorganca): this should recursively cut the template before the first .Response
|
if len(system) == 0 && m.System != "" {
|
||||||
func formatTemplateForResponse(tmpl *template.Template, generate bool) {
|
// add model system prompt since it wasn't provided
|
||||||
var found bool
|
system = append(system, api.Message{Role: "system", Content: m.System})
|
||||||
for i, node := range tmpl.Tree.Root.Nodes {
|
}
|
||||||
if actionNode, ok := node.(*parse.ActionNode); ok {
|
|
||||||
if isResponseNode(actionNode) {
|
// always include the last message
|
||||||
found = true
|
n := len(msgs) - 1
|
||||||
if generate {
|
// in reverse, find all messages that fit into context window
|
||||||
tmpl.Tree.Root.Nodes = tmpl.Tree.Root.Nodes[:i+1]
|
for i := n - 1; i >= 0; i-- {
|
||||||
break
|
var b bytes.Buffer
|
||||||
}
|
if err := m.Template.Execute(&b, template.Values{Messages: append(system, msgs[i:]...)}); err != nil {
|
||||||
}
|
return "", nil, err
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if !found {
|
s, err := tokenize(ctx, b.String())
|
||||||
// add the response node if it doesn't exist
|
|
||||||
responseFieldNode := &parse.FieldNode{NodeType: parse.NodeField, Ident: []string{"Response"}}
|
|
||||||
responsePipeNode := &parse.PipeNode{NodeType: parse.NodePipe, Cmds: []*parse.CommandNode{{NodeType: parse.NodeCommand, Args: []parse.Node{responseFieldNode}}}}
|
|
||||||
responseActionNode := &parse.ActionNode{NodeType: parse.NodeAction, Pipe: responsePipeNode}
|
|
||||||
tmpl.Tree.Root.Nodes = append(tmpl.Tree.Root.Nodes, responseActionNode)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prompt renders a prompt from a template. If generate is set to true,
|
|
||||||
// the response and parts of the template following it are not rendered
|
|
||||||
func Prompt(tmpl *template.Template, system, prompt, response string, generate bool) (string, error) {
|
|
||||||
formatTemplateForResponse(tmpl, generate)
|
|
||||||
|
|
||||||
vars := map[string]any{
|
|
||||||
"System": system,
|
|
||||||
"Prompt": prompt,
|
|
||||||
"Response": response,
|
|
||||||
}
|
|
||||||
|
|
||||||
var sb strings.Builder
|
|
||||||
if err := tmpl.Execute(&sb, vars); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return sb.String(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func countTokens(tmpl *template.Template, system string, prompt string, response string, encode func(string) ([]int, error)) (int, error) {
|
|
||||||
rendered, err := Prompt(tmpl, system, prompt, response, false)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
tokens, err := encode(rendered)
|
|
||||||
if err != nil {
|
|
||||||
slog.Error("failed to encode prompt", "err", err)
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return len(tokens), err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ChatPrompt builds up a prompt from a series of messages, truncating based on context window size
|
|
||||||
func ChatPrompt(tmpl *template.Template, messages []api.Message, window int, encode func(string) ([]int, error)) (string, error) {
|
|
||||||
type prompt struct {
|
|
||||||
System string
|
|
||||||
Prompt string
|
|
||||||
Response string
|
|
||||||
|
|
||||||
images []int
|
|
||||||
tokens int
|
|
||||||
}
|
|
||||||
|
|
||||||
var p prompt
|
|
||||||
|
|
||||||
// iterate through messages to build up {system,user,response} prompts
|
|
||||||
var imgId int
|
|
||||||
var prompts []prompt
|
|
||||||
for _, msg := range messages {
|
|
||||||
switch strings.ToLower(msg.Role) {
|
|
||||||
case "system":
|
|
||||||
if p.System != "" || p.Prompt != "" || p.Response != "" {
|
|
||||||
prompts = append(prompts, p)
|
|
||||||
p = prompt{}
|
|
||||||
}
|
|
||||||
|
|
||||||
p.System = msg.Content
|
|
||||||
case "user":
|
|
||||||
if p.Prompt != "" || p.Response != "" {
|
|
||||||
prompts = append(prompts, p)
|
|
||||||
p = prompt{}
|
|
||||||
}
|
|
||||||
|
|
||||||
var sb strings.Builder
|
|
||||||
for range msg.Images {
|
|
||||||
fmt.Fprintf(&sb, "[img-%d] ", imgId)
|
|
||||||
p.images = append(p.images, imgId)
|
|
||||||
imgId += 1
|
|
||||||
}
|
|
||||||
|
|
||||||
sb.WriteString(msg.Content)
|
|
||||||
p.Prompt = sb.String()
|
|
||||||
case "assistant":
|
|
||||||
if p.Response != "" {
|
|
||||||
prompts = append(prompts, p)
|
|
||||||
p = prompt{}
|
|
||||||
}
|
|
||||||
|
|
||||||
p.Response = msg.Content
|
|
||||||
default:
|
|
||||||
return "", fmt.Errorf("invalid role: %s, role must be one of [system, user, assistant]", msg.Role)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// add final prompt
|
|
||||||
if p.System != "" || p.Prompt != "" || p.Response != "" {
|
|
||||||
prompts = append(prompts, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
// calculate token lengths for each prompt, estimating 768 tokens per images
|
|
||||||
for i, p := range prompts {
|
|
||||||
tokens, err := countTokens(tmpl, p.System, p.Prompt, p.Response, encode)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
prompts[i].tokens = tokens + len(prompts[i].images)*768
|
c := len(s)
|
||||||
}
|
if m.ProjectorPaths != nil {
|
||||||
|
for _, m := range msgs[i:] {
|
||||||
// truncate images and prompts starting from the beginning of the list
|
// images are represented as 768 sized embeddings
|
||||||
// until either one prompt remains or the total tokens fits the context window
|
// TODO: get embedding length from project metadata
|
||||||
// TODO (jmorganca): this doesn't account for the context window room required for the response
|
c += 768 * len(m.Images)
|
||||||
for {
|
}
|
||||||
var required int
|
|
||||||
for _, p := range prompts {
|
|
||||||
required += p.tokens
|
|
||||||
}
|
}
|
||||||
|
|
||||||
required += 1 // for bos token
|
if c > opts.NumCtx {
|
||||||
|
slog.Debug("truncating input messages which exceed context length", "truncated", len(msgs[i:]))
|
||||||
if required <= window {
|
|
||||||
slog.Debug("prompt now fits in context window", "required", required, "window", window)
|
|
||||||
break
|
break
|
||||||
|
} else {
|
||||||
|
n = i
|
||||||
}
|
}
|
||||||
|
|
||||||
prompt := &prompts[0]
|
|
||||||
|
|
||||||
if len(prompt.images) > 1 {
|
|
||||||
img := prompt.images[0]
|
|
||||||
slog.Debug("prompt longer than context window, removing image", "id", img, "required", required, "window", window)
|
|
||||||
prompt.images = prompt.images[1:]
|
|
||||||
prompt.Prompt = strings.Replace(prompt.Prompt, fmt.Sprintf(" [img-%d]", img), "", 1)
|
|
||||||
prompt.tokens -= 768
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(prompts) > 1 {
|
|
||||||
slog.Debug("required tokens longer than context window, removing first prompt", "prompt", prompts[0].tokens, "required", required, "window", window)
|
|
||||||
system := prompt.System
|
|
||||||
prompts = prompts[1:]
|
|
||||||
|
|
||||||
if system != "" && prompts[0].System == "" {
|
|
||||||
prompts[0].System = system
|
|
||||||
|
|
||||||
tokens, err := countTokens(tmpl, prompts[0].System, prompts[0].Prompt, prompts[0].Response, encode)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
prompts[0].tokens = tokens + len(prompts[0].images)*768
|
|
||||||
}
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// stop truncating if there's only one prompt left
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var sb strings.Builder
|
// truncate any messages that do not fit into the context window
|
||||||
for i, p := range prompts {
|
var b bytes.Buffer
|
||||||
// last prompt should leave the response unrendered (for completion)
|
if err := m.Template.Execute(&b, template.Values{Messages: append(system, msgs[n:]...)}); err != nil {
|
||||||
rendered, err := Prompt(tmpl, p.System, p.Prompt, p.Response, i == len(prompts)-1)
|
return "", nil, err
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
sb.WriteString(rendered)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return sb.String(), nil
|
for _, m := range msgs[n:] {
|
||||||
|
for _, i := range m.Images {
|
||||||
|
images = append(images, llm.ImageData{
|
||||||
|
ID: len(images),
|
||||||
|
Data: i,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.String(), images, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -8,208 +10,195 @@ import (
|
|||||||
"github.com/ollama/ollama/template"
|
"github.com/ollama/ollama/template"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestPrompt(t *testing.T) {
|
func tokenize(_ context.Context, s string) (tokens []int, err error) {
|
||||||
tests := []struct {
|
for range strings.Fields(s) {
|
||||||
name string
|
tokens = append(tokens, len(tokens))
|
||||||
template string
|
|
||||||
system string
|
|
||||||
prompt string
|
|
||||||
response string
|
|
||||||
generate bool
|
|
||||||
want string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "simple prompt",
|
|
||||||
template: "[INST] {{ .System }} {{ .Prompt }} [/INST]",
|
|
||||||
system: "You are a Wizard.",
|
|
||||||
prompt: "What are the potion ingredients?",
|
|
||||||
want: "[INST] You are a Wizard. What are the potion ingredients? [/INST]",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "implicit response",
|
|
||||||
template: "[INST] {{ .System }} {{ .Prompt }} [/INST]",
|
|
||||||
system: "You are a Wizard.",
|
|
||||||
prompt: "What are the potion ingredients?",
|
|
||||||
response: "I don't know.",
|
|
||||||
want: "[INST] You are a Wizard. What are the potion ingredients? [/INST]I don't know.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "response",
|
|
||||||
template: "[INST] {{ .System }} {{ .Prompt }} [/INST] {{ .Response }}",
|
|
||||||
system: "You are a Wizard.",
|
|
||||||
prompt: "What are the potion ingredients?",
|
|
||||||
response: "I don't know.",
|
|
||||||
want: "[INST] You are a Wizard. What are the potion ingredients? [/INST] I don't know.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "cut",
|
|
||||||
template: "<system>{{ .System }}</system><user>{{ .Prompt }}</user><assistant>{{ .Response }}</assistant>",
|
|
||||||
system: "You are a Wizard.",
|
|
||||||
prompt: "What are the potion ingredients?",
|
|
||||||
response: "I don't know.",
|
|
||||||
generate: true,
|
|
||||||
want: "<system>You are a Wizard.</system><user>What are the potion ingredients?</user><assistant>I don't know.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "nocut",
|
|
||||||
template: "<system>{{ .System }}</system><user>{{ .Prompt }}</user><assistant>{{ .Response }}</assistant>",
|
|
||||||
system: "You are a Wizard.",
|
|
||||||
prompt: "What are the potion ingredients?",
|
|
||||||
response: "I don't know.",
|
|
||||||
want: "<system>You are a Wizard.</system><user>What are the potion ingredients?</user><assistant>I don't know.</assistant>",
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range tests {
|
return
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
tmpl, err := template.Parse(tc.template)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
got, err := Prompt(tmpl, tc.system, tc.prompt, tc.response, tc.generate)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("error = %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if got != tc.want {
|
|
||||||
t.Errorf("got = %v, want %v", got, tc.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestChatPrompt(t *testing.T) {
|
func TestChatPrompt(t *testing.T) {
|
||||||
tests := []struct {
|
type expect struct {
|
||||||
name string
|
prompt string
|
||||||
template string
|
images [][]byte
|
||||||
messages []api.Message
|
}
|
||||||
window int
|
|
||||||
want string
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
limit int
|
||||||
|
msgs []api.Message
|
||||||
|
expect
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "simple prompt",
|
name: "messages",
|
||||||
template: "[INST] {{ .Prompt }} [/INST]",
|
limit: 64,
|
||||||
messages: []api.Message{
|
msgs: []api.Message{
|
||||||
{Role: "user", Content: "Hello"},
|
{Role: "user", Content: "You're a test, Harry!"},
|
||||||
|
{Role: "assistant", Content: "I-I'm a what?"},
|
||||||
|
{Role: "user", Content: "A test. And a thumping good one at that, I'd wager."},
|
||||||
|
},
|
||||||
|
expect: expect{
|
||||||
|
prompt: "You're a test, Harry! I-I'm a what? A test. And a thumping good one at that, I'd wager. ",
|
||||||
},
|
},
|
||||||
window: 1024,
|
|
||||||
want: "[INST] Hello [/INST]",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "with system message",
|
name: "truncate messages",
|
||||||
template: "[INST] {{ if .System }}<<SYS>>{{ .System }}<</SYS>> {{ end }}{{ .Prompt }} [/INST]",
|
limit: 1,
|
||||||
messages: []api.Message{
|
msgs: []api.Message{
|
||||||
{Role: "system", Content: "You are a Wizard."},
|
{Role: "user", Content: "You're a test, Harry!"},
|
||||||
{Role: "user", Content: "Hello"},
|
{Role: "assistant", Content: "I-I'm a what?"},
|
||||||
|
{Role: "user", Content: "A test. And a thumping good one at that, I'd wager."},
|
||||||
|
},
|
||||||
|
expect: expect{
|
||||||
|
prompt: "A test. And a thumping good one at that, I'd wager. ",
|
||||||
},
|
},
|
||||||
window: 1024,
|
|
||||||
want: "[INST] <<SYS>>You are a Wizard.<</SYS>> Hello [/INST]",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "with response",
|
name: "truncate messages with image",
|
||||||
template: "[INST] {{ if .System }}<<SYS>>{{ .System }}<</SYS>> {{ end }}{{ .Prompt }} [/INST] {{ .Response }}",
|
limit: 64,
|
||||||
messages: []api.Message{
|
msgs: []api.Message{
|
||||||
{Role: "system", Content: "You are a Wizard."},
|
{Role: "user", Content: "You're a test, Harry!"},
|
||||||
{Role: "user", Content: "Hello"},
|
{Role: "assistant", Content: "I-I'm a what?"},
|
||||||
{Role: "assistant", Content: "I am?"},
|
{Role: "user", Content: "A test. And a thumping good one at that, I'd wager.", Images: []api.ImageData{[]byte("something")}},
|
||||||
|
},
|
||||||
|
expect: expect{
|
||||||
|
prompt: "[img-0] A test. And a thumping good one at that, I'd wager. ",
|
||||||
|
images: [][]byte{
|
||||||
|
[]byte("something"),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
window: 1024,
|
|
||||||
want: "[INST] <<SYS>>You are a Wizard.<</SYS>> Hello [/INST] I am?",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "with implicit response",
|
name: "truncate messages with images",
|
||||||
template: "[INST] {{ if .System }}<<SYS>>{{ .System }}<</SYS>> {{ end }}{{ .Prompt }} [/INST]",
|
limit: 64,
|
||||||
messages: []api.Message{
|
msgs: []api.Message{
|
||||||
{Role: "system", Content: "You are a Wizard."},
|
{Role: "user", Content: "You're a test, Harry!", Images: []api.ImageData{[]byte("something")}},
|
||||||
{Role: "user", Content: "Hello"},
|
{Role: "assistant", Content: "I-I'm a what?"},
|
||||||
{Role: "assistant", Content: "I am?"},
|
{Role: "user", Content: "A test. And a thumping good one at that, I'd wager.", Images: []api.ImageData{[]byte("somethingelse")}},
|
||||||
|
},
|
||||||
|
expect: expect{
|
||||||
|
prompt: "[img-0] A test. And a thumping good one at that, I'd wager. ",
|
||||||
|
images: [][]byte{
|
||||||
|
[]byte("somethingelse"),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
window: 1024,
|
|
||||||
want: "[INST] <<SYS>>You are a Wizard.<</SYS>> Hello [/INST]I am?",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "with conversation",
|
name: "messages with images",
|
||||||
template: "[INST] {{ if .System }}<<SYS>>{{ .System }}<</SYS>> {{ end }}{{ .Prompt }} [/INST] {{ .Response }} ",
|
limit: 2048,
|
||||||
messages: []api.Message{
|
msgs: []api.Message{
|
||||||
{Role: "system", Content: "You are a Wizard."},
|
{Role: "user", Content: "You're a test, Harry!", Images: []api.ImageData{[]byte("something")}},
|
||||||
{Role: "user", Content: "What are the potion ingredients?"},
|
{Role: "assistant", Content: "I-I'm a what?"},
|
||||||
{Role: "assistant", Content: "sugar"},
|
{Role: "user", Content: "A test. And a thumping good one at that, I'd wager.", Images: []api.ImageData{[]byte("somethingelse")}},
|
||||||
{Role: "user", Content: "Anything else?"},
|
},
|
||||||
|
expect: expect{
|
||||||
|
prompt: "[img-0] You're a test, Harry! I-I'm a what? [img-1] A test. And a thumping good one at that, I'd wager. ",
|
||||||
|
images: [][]byte{
|
||||||
|
[]byte("something"),
|
||||||
|
[]byte("somethingelse"),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
window: 1024,
|
|
||||||
want: "[INST] <<SYS>>You are a Wizard.<</SYS>> What are the potion ingredients? [/INST] sugar [INST] Anything else? [/INST] ",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "with truncation",
|
name: "message with image tag",
|
||||||
template: "{{ .System }} {{ .Prompt }} {{ .Response }} ",
|
limit: 2048,
|
||||||
messages: []api.Message{
|
msgs: []api.Message{
|
||||||
{Role: "system", Content: "You are a Wizard."},
|
{Role: "user", Content: "You're a test, Harry! [img]", Images: []api.ImageData{[]byte("something")}},
|
||||||
{Role: "user", Content: "Hello"},
|
{Role: "assistant", Content: "I-I'm a what?"},
|
||||||
{Role: "assistant", Content: "I am?"},
|
{Role: "user", Content: "A test. And a thumping good one at that, I'd wager.", Images: []api.ImageData{[]byte("somethingelse")}},
|
||||||
{Role: "user", Content: "Why is the sky blue?"},
|
},
|
||||||
{Role: "assistant", Content: "The sky is blue from rayleigh scattering"},
|
expect: expect{
|
||||||
|
prompt: "You're a test, Harry! [img-0] I-I'm a what? [img-1] A test. And a thumping good one at that, I'd wager. ",
|
||||||
|
images: [][]byte{
|
||||||
|
[]byte("something"),
|
||||||
|
[]byte("somethingelse"),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
window: 10,
|
|
||||||
want: "You are a Wizard. Why is the sky blue? The sky is blue from rayleigh scattering",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "images",
|
name: "messages with interleaved images",
|
||||||
template: "{{ .System }} {{ .Prompt }}",
|
limit: 2048,
|
||||||
messages: []api.Message{
|
msgs: []api.Message{
|
||||||
{Role: "system", Content: "You are a Wizard."},
|
{Role: "user", Content: "You're a test, Harry!"},
|
||||||
{Role: "user", Content: "Hello", Images: []api.ImageData{[]byte("base64")}},
|
{Role: "user", Images: []api.ImageData{[]byte("something")}},
|
||||||
|
{Role: "user", Images: []api.ImageData{[]byte("somethingelse")}},
|
||||||
|
{Role: "assistant", Content: "I-I'm a what?"},
|
||||||
|
{Role: "user", Content: "A test. And a thumping good one at that, I'd wager."},
|
||||||
|
},
|
||||||
|
expect: expect{
|
||||||
|
prompt: "You're a test, Harry!\n\n[img-0]\n\n[img-1] I-I'm a what? A test. And a thumping good one at that, I'd wager. ",
|
||||||
|
images: [][]byte{
|
||||||
|
[]byte("something"),
|
||||||
|
[]byte("somethingelse"),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
window: 1024,
|
|
||||||
want: "You are a Wizard. [img-0] Hello",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "images truncated",
|
name: "truncate message with interleaved images",
|
||||||
template: "{{ .System }} {{ .Prompt }}",
|
limit: 1024,
|
||||||
messages: []api.Message{
|
msgs: []api.Message{
|
||||||
{Role: "system", Content: "You are a Wizard."},
|
{Role: "user", Content: "You're a test, Harry!"},
|
||||||
{Role: "user", Content: "Hello", Images: []api.ImageData{[]byte("img1"), []byte("img2")}},
|
{Role: "user", Images: []api.ImageData{[]byte("something")}},
|
||||||
|
{Role: "user", Images: []api.ImageData{[]byte("somethingelse")}},
|
||||||
|
{Role: "assistant", Content: "I-I'm a what?"},
|
||||||
|
{Role: "user", Content: "A test. And a thumping good one at that, I'd wager."},
|
||||||
|
},
|
||||||
|
expect: expect{
|
||||||
|
prompt: "[img-0] I-I'm a what? A test. And a thumping good one at that, I'd wager. ",
|
||||||
|
images: [][]byte{
|
||||||
|
[]byte("somethingelse"),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
window: 1024,
|
|
||||||
want: "You are a Wizard. [img-0] [img-1] Hello",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "empty list",
|
name: "message with system prompt",
|
||||||
template: "{{ .System }} {{ .Prompt }}",
|
limit: 2048,
|
||||||
messages: []api.Message{},
|
msgs: []api.Message{
|
||||||
window: 1024,
|
{Role: "system", Content: "You are the Test Who Lived."},
|
||||||
want: "",
|
{Role: "user", Content: "You're a test, Harry!"},
|
||||||
},
|
{Role: "assistant", Content: "I-I'm a what?"},
|
||||||
{
|
{Role: "user", Content: "A test. And a thumping good one at that, I'd wager."},
|
||||||
name: "empty prompt",
|
},
|
||||||
template: "[INST] {{ if .System }}<<SYS>>{{ .System }}<</SYS>> {{ end }}{{ .Prompt }} [/INST] {{ .Response }} ",
|
expect: expect{
|
||||||
messages: []api.Message{
|
prompt: "You're a test, Harry! I-I'm a what? You are the Test Who Lived. A test. And a thumping good one at that, I'd wager. ",
|
||||||
{Role: "user", Content: ""},
|
|
||||||
},
|
},
|
||||||
window: 1024,
|
|
||||||
want: "",
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
encode := func(s string) ([]int, error) {
|
tmpl, err := template.Parse(`
|
||||||
words := strings.Fields(s)
|
{{- if .System }}{{ .System }} {{ end }}
|
||||||
return make([]int, len(words)), nil
|
{{- if .Prompt }}{{ .Prompt }} {{ end }}
|
||||||
|
{{- if .Response }}{{ .Response }} {{ end }}`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range tests {
|
for _, tt := range cases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
tmpl, err := template.Parse(tc.template)
|
model := Model{Template: tmpl, ProjectorPaths: []string{"vision"}}
|
||||||
|
opts := api.Options{Runner: api.Runner{NumCtx: tt.limit}}
|
||||||
|
prompt, images, err := chatPrompt(context.TODO(), &model, tokenize, &opts, tt.msgs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
got, err := ChatPrompt(tmpl, tc.messages, tc.window, encode)
|
if tt.prompt != prompt {
|
||||||
if err != nil {
|
t.Errorf("expected %q, got %q", tt.prompt, prompt)
|
||||||
t.Errorf("error = %v", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if got != tc.want {
|
if len(images) != len(tt.images) {
|
||||||
t.Errorf("got: %q, want: %q", got, tc.want)
|
t.Fatalf("expected %d images, got %d", len(tt.images), len(images))
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range images {
|
||||||
|
if images[i].ID != i {
|
||||||
|
t.Errorf("expected ID %d, got %d", i, images[i].ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(images[i].Data, tt.images[i]) {
|
||||||
|
t.Errorf("expected %q, got %q", tt.images[i], images[i])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
505
server/routes.go
505
server/routes.go
@@ -1,13 +1,13 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"cmp"
|
"cmp"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/fs"
|
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -54,6 +54,8 @@ func init() {
|
|||||||
gin.SetMode(mode)
|
gin.SetMode(mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var errRequired = errors.New("is required")
|
||||||
|
|
||||||
func modelOptions(model *Model, requestOpts map[string]interface{}) (api.Options, error) {
|
func modelOptions(model *Model, requestOpts map[string]interface{}) (api.Options, error) {
|
||||||
opts := api.DefaultOptions()
|
opts := api.DefaultOptions()
|
||||||
if err := opts.FromMap(model.Options); err != nil {
|
if err := opts.FromMap(model.Options); err != nil {
|
||||||
@@ -67,163 +69,147 @@ func modelOptions(model *Model, requestOpts map[string]interface{}) (api.Options
|
|||||||
return opts, nil
|
return opts, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func isSupportedImageType(image []byte) bool {
|
// scheduleRunner schedules a runner after validating inputs such as capabilities and model options.
|
||||||
contentType := http.DetectContentType(image)
|
// It returns the allocated runner, model instance, and consolidated options if successful and error otherwise.
|
||||||
allowedTypes := []string{"image/jpeg", "image/jpg", "image/png"}
|
func (s *Server) scheduleRunner(ctx context.Context, name string, mTemplate string, caps []Capability, requestOpts map[string]any, keepAlive *api.Duration) (llm.LlamaServer, *Model, *api.Options, error) {
|
||||||
return slices.Contains(allowedTypes, contentType)
|
if name == "" {
|
||||||
|
return nil, nil, nil, fmt.Errorf("model %w", errRequired)
|
||||||
|
}
|
||||||
|
|
||||||
|
model, err := GetModel(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if mTemplate != "" {
|
||||||
|
model.Template, err = template.Parse(mTemplate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := model.CheckCapabilities(caps...); err != nil {
|
||||||
|
return nil, nil, nil, fmt.Errorf("%s %w", name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
opts, err := modelOptions(model, requestOpts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
runnerCh, errCh := s.sched.GetRunner(ctx, model, opts, keepAlive)
|
||||||
|
var runner *runnerRef
|
||||||
|
select {
|
||||||
|
case runner = <-runnerCh:
|
||||||
|
case err = <-errCh:
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return runner.llama, model, &opts, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) GenerateHandler(c *gin.Context) {
|
func (s *Server) GenerateHandler(c *gin.Context) {
|
||||||
checkpointStart := time.Now()
|
|
||||||
var req api.GenerateRequest
|
var req api.GenerateRequest
|
||||||
err := c.ShouldBindJSON(&req)
|
if err := c.ShouldBindJSON(&req); errors.Is(err, io.EOF) {
|
||||||
|
|
||||||
switch {
|
|
||||||
case errors.Is(err, io.EOF):
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "missing request body"})
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "missing request body"})
|
||||||
return
|
return
|
||||||
case err != nil:
|
} else if err != nil {
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// validate the request
|
if req.Format != "" && req.Format != "json" {
|
||||||
switch {
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "format must be empty or \"json\""})
|
||||||
case req.Model == "":
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "model is required"})
|
|
||||||
return
|
return
|
||||||
case len(req.Format) > 0 && req.Format != "json":
|
} else if req.Raw && (req.Template != "" || req.System != "" || len(req.Context) > 0) {
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "format must be json"})
|
|
||||||
return
|
|
||||||
case req.Raw && (req.Template != "" || req.System != "" || len(req.Context) > 0):
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "raw mode does not support template, system, or context"})
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "raw mode does not support template, system, or context"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, img := range req.Images {
|
caps := []Capability{CapabilityCompletion}
|
||||||
if !isSupportedImageType(img) {
|
r, m, opts, err := s.scheduleRunner(c.Request.Context(), req.Model, "", caps, req.Options, req.KeepAlive)
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "unsupported image format"})
|
if errors.Is(err, errCapabilityCompletion) {
|
||||||
return
|
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("%q does not support generate", req.Model)})
|
||||||
}
|
return
|
||||||
}
|
} else if err != nil {
|
||||||
|
handleScheduleError(c, req.Model, err)
|
||||||
model, err := GetModel(req.Model)
|
|
||||||
if err != nil {
|
|
||||||
var pErr *fs.PathError
|
|
||||||
if errors.As(err, &pErr) {
|
|
||||||
c.JSON(http.StatusNotFound, gin.H{"error": fmt.Sprintf("model '%s' not found, try pulling it first", req.Model)})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !model.Has(CapabilityCompletion) {
|
if req.Prompt == "" {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("%s does not support generate", req.Model)})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
opts, err := modelOptions(model, req.Options)
|
|
||||||
if err != nil {
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
rCh, eCh := s.sched.GetRunner(c.Request.Context(), model, opts, req.KeepAlive)
|
|
||||||
var runner *runnerRef
|
|
||||||
select {
|
|
||||||
case runner = <-rCh:
|
|
||||||
case err = <-eCh:
|
|
||||||
handleErrorResponse(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// an empty request loads the model
|
|
||||||
// note: for a short while template was used in lieu
|
|
||||||
// of `raw` mode so we need to check for it too
|
|
||||||
if req.Prompt == "" && req.Template == "" && req.System == "" {
|
|
||||||
c.JSON(http.StatusOK, api.GenerateResponse{
|
c.JSON(http.StatusOK, api.GenerateResponse{
|
||||||
CreatedAt: time.Now().UTC(),
|
|
||||||
Model: req.Model,
|
Model: req.Model,
|
||||||
|
CreatedAt: time.Now().UTC(),
|
||||||
Done: true,
|
Done: true,
|
||||||
DoneReason: "load",
|
DoneReason: "load",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
tmpl, err := template.Parse(req.Template)
|
images := make([]llm.ImageData, len(req.Images))
|
||||||
if err != nil {
|
for i := range req.Images {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
images[i] = llm.ImageData{ID: i, Data: req.Images[i]}
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
checkpointLoaded := time.Now()
|
prompt := req.Prompt
|
||||||
|
if !req.Raw {
|
||||||
var prompt string
|
var msgs []api.Message
|
||||||
switch {
|
if req.System != "" {
|
||||||
case req.Raw:
|
msgs = append(msgs, api.Message{Role: "system", Content: req.System})
|
||||||
prompt = req.Prompt
|
} else if m.System != "" {
|
||||||
case req.Prompt != "":
|
msgs = append(msgs, api.Message{Role: "system", Content: m.System})
|
||||||
if req.Template == "" {
|
|
||||||
tmpl = model.Template
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if req.System == "" {
|
for _, i := range images {
|
||||||
req.System = model.System
|
msgs = append(msgs, api.Message{Role: "user", Content: fmt.Sprintf("[img-%d]", i.ID)})
|
||||||
}
|
}
|
||||||
|
|
||||||
slog.Debug("generate handler", "prompt", req.Prompt)
|
msgs = append(msgs, api.Message{Role: "user", Content: req.Prompt})
|
||||||
slog.Debug("generate handler", "template", req.Template)
|
|
||||||
slog.Debug("generate handler", "system", req.System)
|
|
||||||
|
|
||||||
var sb strings.Builder
|
tmpl := m.Template
|
||||||
for i := range req.Images {
|
if req.Template != "" {
|
||||||
fmt.Fprintf(&sb, "[img-%d] ", i)
|
tmpl, err = template.Parse(req.Template)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sb.WriteString(req.Prompt)
|
var b bytes.Buffer
|
||||||
|
|
||||||
p, err := Prompt(tmpl, req.System, sb.String(), "", true)
|
|
||||||
if err != nil {
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
sb.Reset()
|
|
||||||
if req.Context != nil {
|
if req.Context != nil {
|
||||||
prev, err := runner.llama.Detokenize(c.Request.Context(), req.Context)
|
s, err := r.Detokenize(c.Request.Context(), req.Context)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
sb.WriteString(prev)
|
b.WriteString(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
sb.WriteString(p)
|
if err := tmpl.Execute(&b, template.Values{Messages: msgs}); err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
prompt = sb.String()
|
prompt = b.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
slog.Debug("generate handler", "prompt", prompt)
|
slog.Debug("generate request", "prompt", prompt, "images", images)
|
||||||
|
|
||||||
ch := make(chan any)
|
ch := make(chan any)
|
||||||
var generated strings.Builder
|
|
||||||
go func() {
|
go func() {
|
||||||
defer close(ch)
|
defer close(ch)
|
||||||
|
if err := r.Completion(c.Request.Context(), llm.CompletionRequest{
|
||||||
fn := func(r llm.CompletionResponse) {
|
Prompt: prompt,
|
||||||
// Build up the full response
|
Images: images,
|
||||||
if _, err := generated.WriteString(r.Content); err != nil {
|
Format: req.Format,
|
||||||
ch <- gin.H{"error": err.Error()}
|
Options: opts,
|
||||||
return
|
}, func(r llm.CompletionResponse) {
|
||||||
}
|
ch <- api.GenerateResponse{
|
||||||
|
|
||||||
resp := api.GenerateResponse{
|
|
||||||
Model: req.Model,
|
Model: req.Model,
|
||||||
CreatedAt: time.Now().UTC(),
|
CreatedAt: time.Now().UTC(),
|
||||||
Done: r.Done,
|
|
||||||
Response: r.Content,
|
Response: r.Content,
|
||||||
|
Done: r.Done,
|
||||||
DoneReason: r.DoneReason,
|
DoneReason: r.DoneReason,
|
||||||
Metrics: api.Metrics{
|
Metrics: api.Metrics{
|
||||||
PromptEvalCount: r.PromptEvalCount,
|
PromptEvalCount: r.PromptEvalCount,
|
||||||
@@ -232,77 +218,35 @@ func (s *Server) GenerateHandler(c *gin.Context) {
|
|||||||
EvalDuration: r.EvalDuration,
|
EvalDuration: r.EvalDuration,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
}); err != nil {
|
||||||
if r.Done {
|
|
||||||
resp.TotalDuration = time.Since(checkpointStart)
|
|
||||||
resp.LoadDuration = checkpointLoaded.Sub(checkpointStart)
|
|
||||||
|
|
||||||
if !req.Raw {
|
|
||||||
p, err := Prompt(tmpl, req.System, req.Prompt, generated.String(), false)
|
|
||||||
if err != nil {
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO (jmorganca): encode() should not strip special tokens
|
|
||||||
tokens, err := runner.llama.Tokenize(c.Request.Context(), p)
|
|
||||||
if err != nil {
|
|
||||||
ch <- gin.H{"error": err.Error()}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
resp.Context = append(req.Context, tokens...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ch <- resp
|
|
||||||
}
|
|
||||||
|
|
||||||
var images []llm.ImageData
|
|
||||||
for i := range req.Images {
|
|
||||||
images = append(images, llm.ImageData{
|
|
||||||
ID: i,
|
|
||||||
Data: req.Images[i],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start prediction
|
|
||||||
req := llm.CompletionRequest{
|
|
||||||
Prompt: prompt,
|
|
||||||
Format: req.Format,
|
|
||||||
Images: images,
|
|
||||||
Options: opts,
|
|
||||||
}
|
|
||||||
if err := runner.llama.Completion(c.Request.Context(), req, fn); err != nil {
|
|
||||||
ch <- gin.H{"error": err.Error()}
|
ch <- gin.H{"error": err.Error()}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if req.Stream != nil && !*req.Stream {
|
if req.Stream != nil && !*req.Stream {
|
||||||
// Accumulate responses into the final response
|
var r api.GenerateResponse
|
||||||
var final api.GenerateResponse
|
|
||||||
var sb strings.Builder
|
var sb strings.Builder
|
||||||
for resp := range ch {
|
for rr := range ch {
|
||||||
switch r := resp.(type) {
|
switch t := rr.(type) {
|
||||||
case api.GenerateResponse:
|
case api.GenerateResponse:
|
||||||
sb.WriteString(r.Response)
|
sb.WriteString(t.Response)
|
||||||
final = r
|
r = t
|
||||||
case gin.H:
|
case gin.H:
|
||||||
if errorMsg, ok := r["error"].(string); ok {
|
msg, ok := t["error"].(string)
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": errorMsg})
|
if !ok {
|
||||||
return
|
msg = "unexpected error format in response"
|
||||||
} else {
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "unexpected error format in response"})
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": msg})
|
||||||
|
return
|
||||||
default:
|
default:
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "unexpected error"})
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "unexpected response"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final.Response = sb.String()
|
r.Response = sb.String()
|
||||||
c.JSON(http.StatusOK, final)
|
c.JSON(http.StatusOK, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -311,44 +255,17 @@ func (s *Server) GenerateHandler(c *gin.Context) {
|
|||||||
|
|
||||||
func (s *Server) EmbeddingsHandler(c *gin.Context) {
|
func (s *Server) EmbeddingsHandler(c *gin.Context) {
|
||||||
var req api.EmbeddingRequest
|
var req api.EmbeddingRequest
|
||||||
err := c.ShouldBindJSON(&req)
|
if err := c.ShouldBindJSON(&req); errors.Is(err, io.EOF) {
|
||||||
switch {
|
|
||||||
case errors.Is(err, io.EOF):
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "missing request body"})
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "missing request body"})
|
||||||
return
|
return
|
||||||
case err != nil:
|
} else if err != nil {
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if req.Model == "" {
|
r, _, _, err := s.scheduleRunner(c.Request.Context(), req.Model, "", []Capability{}, req.Options, req.KeepAlive)
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "model is required"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
model, err := GetModel(req.Model)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var pErr *fs.PathError
|
handleScheduleError(c, req.Model, err)
|
||||||
if errors.As(err, &pErr) {
|
|
||||||
c.JSON(http.StatusNotFound, gin.H{"error": fmt.Sprintf("model '%s' not found, try pulling it first", req.Model)})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
opts, err := modelOptions(model, req.Options)
|
|
||||||
if err != nil {
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
rCh, eCh := s.sched.GetRunner(c.Request.Context(), model, opts, req.KeepAlive)
|
|
||||||
var runner *runnerRef
|
|
||||||
select {
|
|
||||||
case runner = <-rCh:
|
|
||||||
case err = <-eCh:
|
|
||||||
handleErrorResponse(c, err)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -358,17 +275,14 @@ func (s *Server) EmbeddingsHandler(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
embedding, err := runner.llama.Embedding(c.Request.Context(), req.Prompt)
|
embedding, err := r.Embedding(c.Request.Context(), req.Prompt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Info(fmt.Sprintf("embedding generation failed: %v", err))
|
slog.Info(fmt.Sprintf("embedding generation failed: %v", err))
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to generate embedding"})
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to generate embedding"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
resp := api.EmbeddingResponse{
|
c.JSON(http.StatusOK, api.EmbeddingResponse{Embedding: embedding})
|
||||||
Embedding: embedding,
|
|
||||||
}
|
|
||||||
c.JSON(http.StatusOK, resp)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) PullModelHandler(c *gin.Context) {
|
func (s *Server) PullModelHandler(c *gin.Context) {
|
||||||
@@ -649,9 +563,9 @@ func GetModelInfo(req api.ShowRequest) (*api.ShowResponse, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
msgs := make([]api.Message, 0)
|
msgs := make([]api.Message, len(m.Messages))
|
||||||
for _, msg := range m.Messages {
|
for i, msg := range m.Messages {
|
||||||
msgs = append(msgs, api.Message{Role: msg.Role, Content: msg.Content})
|
msgs[i] = api.Message{Role: msg.Role, Content: msg.Content}
|
||||||
}
|
}
|
||||||
|
|
||||||
n := model.ParseName(req.Model)
|
n := model.ParseName(req.Model)
|
||||||
@@ -1214,132 +1128,55 @@ func (s *Server) ProcessHandler(c *gin.Context) {
|
|||||||
c.JSON(http.StatusOK, api.ProcessResponse{Models: models})
|
c.JSON(http.StatusOK, api.ProcessResponse{Models: models})
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChatPrompt builds up a prompt from a series of messages for the currently `loaded` model
|
|
||||||
func chatPrompt(ctx context.Context, runner *runnerRef, template *template.Template, messages []api.Message, numCtx int) (string, error) {
|
|
||||||
encode := func(s string) ([]int, error) {
|
|
||||||
return runner.llama.Tokenize(ctx, s)
|
|
||||||
}
|
|
||||||
|
|
||||||
prompt, err := ChatPrompt(template, messages, numCtx, encode)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return prompt, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) ChatHandler(c *gin.Context) {
|
func (s *Server) ChatHandler(c *gin.Context) {
|
||||||
checkpointStart := time.Now()
|
|
||||||
|
|
||||||
var req api.ChatRequest
|
var req api.ChatRequest
|
||||||
err := c.ShouldBindJSON(&req)
|
if err := c.ShouldBindJSON(&req); errors.Is(err, io.EOF) {
|
||||||
switch {
|
|
||||||
case errors.Is(err, io.EOF):
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "missing request body"})
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "missing request body"})
|
||||||
return
|
return
|
||||||
case err != nil:
|
} else if err != nil {
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// validate the request
|
caps := []Capability{CapabilityCompletion}
|
||||||
switch {
|
r, m, opts, err := s.scheduleRunner(c.Request.Context(), req.Model, req.Template, caps, req.Options, req.KeepAlive)
|
||||||
case req.Model == "":
|
if errors.Is(err, errCapabilityCompletion) {
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "model is required"})
|
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("%q does not support chat", req.Model)})
|
||||||
return
|
return
|
||||||
case len(req.Format) > 0 && req.Format != "json":
|
} else if err != nil {
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "format must be json"})
|
handleScheduleError(c, req.Model, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
model, err := GetModel(req.Model)
|
if len(req.Messages) == 0 {
|
||||||
if err != nil {
|
c.JSON(http.StatusOK, api.ChatResponse{
|
||||||
var pErr *fs.PathError
|
|
||||||
if errors.As(err, &pErr) {
|
|
||||||
c.JSON(http.StatusNotFound, gin.H{"error": fmt.Sprintf("model '%s' not found, try pulling it first", req.Model)})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !model.Has(CapabilityCompletion) {
|
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("%s does not support chat", req.Model)})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
opts, err := modelOptions(model, req.Options)
|
|
||||||
if err != nil {
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
rCh, eCh := s.sched.GetRunner(c.Request.Context(), model, opts, req.KeepAlive)
|
|
||||||
var runner *runnerRef
|
|
||||||
select {
|
|
||||||
case runner = <-rCh:
|
|
||||||
case err = <-eCh:
|
|
||||||
handleErrorResponse(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
checkpointLoaded := time.Now()
|
|
||||||
|
|
||||||
// if the first message is not a system message, then add the model's default system message
|
|
||||||
if len(req.Messages) > 0 && req.Messages[0].Role != "system" {
|
|
||||||
req.Messages = append([]api.Message{
|
|
||||||
{
|
|
||||||
Role: "system",
|
|
||||||
Content: model.System,
|
|
||||||
},
|
|
||||||
}, req.Messages...)
|
|
||||||
}
|
|
||||||
|
|
||||||
prompt, err := chatPrompt(c.Request.Context(), runner, model.Template, req.Messages, opts.NumCtx)
|
|
||||||
if err != nil {
|
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// an empty request loads the model
|
|
||||||
if len(req.Messages) == 0 || prompt == "" {
|
|
||||||
resp := api.ChatResponse{
|
|
||||||
CreatedAt: time.Now().UTC(),
|
|
||||||
Model: req.Model,
|
Model: req.Model,
|
||||||
|
CreatedAt: time.Now().UTC(),
|
||||||
|
Message: api.Message{Role: "assistant"},
|
||||||
Done: true,
|
Done: true,
|
||||||
DoneReason: "load",
|
DoneReason: "load",
|
||||||
Message: api.Message{Role: "assistant"},
|
})
|
||||||
}
|
|
||||||
c.JSON(http.StatusOK, resp)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// only send images that are in the prompt
|
prompt, images, err := chatPrompt(c.Request.Context(), m, r.Tokenize, opts, req.Messages)
|
||||||
var i int
|
if err != nil {
|
||||||
var images []llm.ImageData
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||||
for _, m := range req.Messages {
|
return
|
||||||
for _, img := range m.Images {
|
|
||||||
if !isSupportedImageType(img) {
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "unsupported image format"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.Contains(prompt, fmt.Sprintf("[img-%d]", i)) {
|
|
||||||
images = append(images, llm.ImageData{Data: img, ID: i})
|
|
||||||
}
|
|
||||||
i += 1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
slog.Debug("chat handler", "prompt", prompt, "images", len(images))
|
slog.Debug("chat request", "images", len(images), "prompt", prompt)
|
||||||
|
|
||||||
ch := make(chan any)
|
ch := make(chan any)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
defer close(ch)
|
defer close(ch)
|
||||||
|
if err := r.Completion(c.Request.Context(), llm.CompletionRequest{
|
||||||
fn := func(r llm.CompletionResponse) {
|
Prompt: prompt,
|
||||||
resp := api.ChatResponse{
|
Images: images,
|
||||||
|
Format: req.Format,
|
||||||
|
Options: opts,
|
||||||
|
}, func(r llm.CompletionResponse) {
|
||||||
|
ch <- api.ChatResponse{
|
||||||
Model: req.Model,
|
Model: req.Model,
|
||||||
CreatedAt: time.Now().UTC(),
|
CreatedAt: time.Now().UTC(),
|
||||||
Message: api.Message{Role: "assistant", Content: r.Content},
|
Message: api.Message{Role: "assistant", Content: r.Content},
|
||||||
@@ -1352,64 +1189,52 @@ func (s *Server) ChatHandler(c *gin.Context) {
|
|||||||
EvalDuration: r.EvalDuration,
|
EvalDuration: r.EvalDuration,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
}); err != nil {
|
||||||
if r.Done {
|
|
||||||
resp.TotalDuration = time.Since(checkpointStart)
|
|
||||||
resp.LoadDuration = checkpointLoaded.Sub(checkpointStart)
|
|
||||||
}
|
|
||||||
|
|
||||||
ch <- resp
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := runner.llama.Completion(c.Request.Context(), llm.CompletionRequest{
|
|
||||||
Prompt: prompt,
|
|
||||||
Format: req.Format,
|
|
||||||
Images: images,
|
|
||||||
Options: opts,
|
|
||||||
}, fn); err != nil {
|
|
||||||
ch <- gin.H{"error": err.Error()}
|
ch <- gin.H{"error": err.Error()}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if req.Stream != nil && !*req.Stream {
|
if req.Stream != nil && !*req.Stream {
|
||||||
// Accumulate responses into the final response
|
var r api.ChatResponse
|
||||||
var final api.ChatResponse
|
|
||||||
var sb strings.Builder
|
var sb strings.Builder
|
||||||
for resp := range ch {
|
for rr := range ch {
|
||||||
switch r := resp.(type) {
|
switch t := rr.(type) {
|
||||||
case api.ChatResponse:
|
case api.ChatResponse:
|
||||||
sb.WriteString(r.Message.Content)
|
sb.WriteString(t.Message.Content)
|
||||||
final = r
|
r = t
|
||||||
case gin.H:
|
case gin.H:
|
||||||
if errorMsg, ok := r["error"].(string); ok {
|
msg, ok := t["error"].(string)
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": errorMsg})
|
if !ok {
|
||||||
return
|
msg = "unexpected error format in response"
|
||||||
} else {
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "unexpected error format in response"})
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": msg})
|
||||||
|
return
|
||||||
default:
|
default:
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "unexpected error"})
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "unexpected response"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final.Message = api.Message{Role: "assistant", Content: sb.String()}
|
r.Message.Content = sb.String()
|
||||||
c.JSON(http.StatusOK, final)
|
c.JSON(http.StatusOK, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
streamResponse(c, ch)
|
streamResponse(c, ch)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleErrorResponse(c *gin.Context, err error) {
|
func handleScheduleError(c *gin.Context, name string, err error) {
|
||||||
if errors.Is(err, context.Canceled) {
|
switch {
|
||||||
|
case errors.Is(err, errRequired):
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||||
|
case errors.Is(err, context.Canceled):
|
||||||
c.JSON(499, gin.H{"error": "request canceled"})
|
c.JSON(499, gin.H{"error": "request canceled"})
|
||||||
return
|
case errors.Is(err, ErrMaxQueue):
|
||||||
}
|
|
||||||
if errors.Is(err, ErrMaxQueue) {
|
|
||||||
c.JSON(http.StatusServiceUnavailable, gin.H{"error": err.Error()})
|
c.JSON(http.StatusServiceUnavailable, gin.H{"error": err.Error()})
|
||||||
return
|
case errors.Is(err, os.ErrNotExist):
|
||||||
|
c.JSON(http.StatusNotFound, gin.H{"error": fmt.Sprintf("model %q not found, try pulling it first", name)})
|
||||||
|
default:
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||||
}
|
}
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -545,9 +545,9 @@ func TestCreateDetectTemplate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
checkFileExists(t, filepath.Join(p, "blobs", "*"), []string{
|
checkFileExists(t, filepath.Join(p, "blobs", "*"), []string{
|
||||||
filepath.Join(p, "blobs", "sha256-2f8e594e6f34b1b4d36a246628eeb3365ce442303d656f1fcc69e821722acea0"),
|
|
||||||
filepath.Join(p, "blobs", "sha256-542b217f179c7825eeb5bca3c77d2b75ed05bafbd3451d9188891a60a85337c6"),
|
|
||||||
filepath.Join(p, "blobs", "sha256-553c4a3f747b3d22a4946875f1cc8ed011c2930d83f864a0c7265f9ec0a20413"),
|
filepath.Join(p, "blobs", "sha256-553c4a3f747b3d22a4946875f1cc8ed011c2930d83f864a0c7265f9ec0a20413"),
|
||||||
|
filepath.Join(p, "blobs", "sha256-9512c372dfc7d84d6065b8dd2b601aeed8cc1a78e7a7aa784a42fff37f5524b7"),
|
||||||
|
filepath.Join(p, "blobs", "sha256-b8b78cb8c6eefd14c06f1af042e6161255bf87bbf2dd14fce57cdac893db8139"),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1,8 @@
|
|||||||
|
{{- if .Messages }}
|
||||||
|
{{- if .System }}<start_system>{{ .System }}<end_message>
|
||||||
|
{{- end }}
|
||||||
|
{{- range .Messages }}<start_{{ .Role }}>{{ .Content }}<end_message>
|
||||||
|
{{- end }}<start_assistant>
|
||||||
|
{{- else }}
|
||||||
{{ if .System }}<start_system>{{ .System }}<end_message>{{ end }}{{ if .Prompt }}<start_user>{{ .Prompt }}<end_message>{{ end }}<start_assistant>{{ .Response }}<end_message>
|
{{ if .System }}<start_system>{{ .System }}<end_message>{{ end }}{{ if .Prompt }}<start_user>{{ .Prompt }}<end_message>{{ end }}<start_assistant>{{ .Response }}<end_message>
|
||||||
|
{{- end }}
|
||||||
@@ -1,3 +1,14 @@
|
|||||||
|
{{- if .Messages }}
|
||||||
|
{{- if .System }}{{ .System }}
|
||||||
|
{{- end }}
|
||||||
|
{{- range .Messages }}
|
||||||
|
{{- if eq .Role "user" }}### Instruction:
|
||||||
|
{{- else if eq .Role "assistant" }}### Response:
|
||||||
|
{{- end }}
|
||||||
|
{{ .Content }}
|
||||||
|
|
||||||
|
{{ end }}### Response:
|
||||||
|
{{ else }}
|
||||||
{{ if .System }}{{ .System }}
|
{{ if .System }}{{ .System }}
|
||||||
|
|
||||||
{{ end }}{{ if .Prompt }}### Instruction:
|
{{ end }}{{ if .Prompt }}### Instruction:
|
||||||
@@ -5,3 +16,4 @@
|
|||||||
|
|
||||||
{{ end }}### Response:
|
{{ end }}### Response:
|
||||||
{{ .Response }}
|
{{ .Response }}
|
||||||
|
{{- end }}
|
||||||
@@ -1,6 +1,15 @@
|
|||||||
|
{{- if .Messages }}
|
||||||
|
{{- if .System }}<|im_start|>system
|
||||||
|
{{ .System }}<|im_end|>
|
||||||
|
{{ end }}
|
||||||
|
{{- range .Messages }}<|im_start|>{{ .Role }}
|
||||||
|
{{ .Content }}<|im_end|>
|
||||||
|
{{ end }}<|im_start|>assistant
|
||||||
|
{{ else }}
|
||||||
{{ if .System }}<|im_start|>system
|
{{ if .System }}<|im_start|>system
|
||||||
{{ .System }}<|im_end|>
|
{{ .System }}<|im_end|>
|
||||||
{{ end }}{{ if .Prompt }}<|im_start|>user
|
{{ end }}{{ if .Prompt }}<|im_start|>user
|
||||||
{{ .Prompt }}<|im_end|>
|
{{ .Prompt }}<|im_end|>
|
||||||
{{ end }}<|im_start|>assistant
|
{{ end }}<|im_start|>assistant
|
||||||
{{ .Response }}<|im_end|>
|
{{ .Response }}<|im_end|>
|
||||||
|
{{- end }}
|
||||||
@@ -1,5 +1,17 @@
|
|||||||
|
{{- if .Messages }}
|
||||||
|
{{- if .System }}System: {{ .System }}
|
||||||
|
|
||||||
|
{{ end }}
|
||||||
|
{{- range .Messages }}
|
||||||
|
{{- if eq .Role "user" }}User:
|
||||||
|
{{- else if eq .Role "assistant" }}Assistant:
|
||||||
|
{{- end }} {{ .Content }}
|
||||||
|
|
||||||
|
{{ end }}Assistant:
|
||||||
|
{{- else }}
|
||||||
{{ if .System }}System: {{ .System }}
|
{{ if .System }}System: {{ .System }}
|
||||||
|
|
||||||
{{ end }}{{ if .Prompt }}User: {{ .Prompt }}
|
{{ end }}{{ if .Prompt }}User: {{ .Prompt }}
|
||||||
|
|
||||||
{{ end }}Assistant: <|begin_of_text|>{{ .Response }}
|
{{ end }}Assistant: <|begin_of_text|>{{ .Response }}
|
||||||
|
{{- end }}
|
||||||
@@ -1,3 +1,13 @@
|
|||||||
|
{{- if .Messages }}
|
||||||
|
{{- if .System }}Source: system
|
||||||
|
|
||||||
|
{{ .System }} <step> {{ end }}
|
||||||
|
{{- range .Messages }}Source: {{ .Role }}
|
||||||
|
|
||||||
|
{{ .Content }} <step> {{ end }}Source: assistant
|
||||||
|
Destination: user
|
||||||
|
|
||||||
|
{{ else }}
|
||||||
{{ if .System }} Source: system
|
{{ if .System }} Source: system
|
||||||
|
|
||||||
{{ .System }} <step>{{ end }} Source: user
|
{{ .System }} <step>{{ end }} Source: user
|
||||||
@@ -6,3 +16,4 @@
|
|||||||
Destination: user
|
Destination: user
|
||||||
|
|
||||||
{{ .Response }}<step>
|
{{ .Response }}<step>
|
||||||
|
{{- end }}
|
||||||
@@ -1,3 +1,13 @@
|
|||||||
|
{{- if .Messages }}
|
||||||
|
{{- if .System }}System: {{ .System }}
|
||||||
|
{{ end }}
|
||||||
|
{{- range .Messages }}
|
||||||
|
{{- if eq .Role "user" }}User:
|
||||||
|
{{ else if eq .Role "assistant" }}Falcon:
|
||||||
|
{{ end }}{{ .Content }}
|
||||||
|
{{ end }}Falcon:
|
||||||
|
{{ else }}
|
||||||
{{ if .System }}{{ .System }}
|
{{ if .System }}{{ .System }}
|
||||||
{{ end }}{{ if .Prompt }}User: {{ .Prompt }}
|
{{ end }}{{ if .Prompt }}User: {{ .Prompt }}
|
||||||
{{ end }}Assistant: {{ .Response }}
|
{{ end }}Assistant: {{ .Response }}
|
||||||
|
{{- end }}
|
||||||
@@ -1,4 +1,16 @@
|
|||||||
|
{{- if .Messages }}
|
||||||
|
{{- range $index, $_ := .Messages }}<start_of_turn>
|
||||||
|
{{- if eq .Role "user" }}user
|
||||||
|
{{- if and $.System (eq $index 0) }}
|
||||||
|
{{ $.System }}
|
||||||
|
{{- end }}
|
||||||
|
{{- else if eq .Role "assistant" }}model
|
||||||
|
{{- end }}
|
||||||
|
{{ .Content }}<end_of_turn>
|
||||||
|
{{ end }}<start_of_turn>model
|
||||||
|
{{ else }}
|
||||||
<start_of_turn>user
|
<start_of_turn>user
|
||||||
{{ if .System }}{{ .System }} {{ end }}{{ .Prompt }}<end_of_turn>
|
{{ if .System }}{{ .System }} {{ end }}{{ .Prompt }}<end_of_turn>
|
||||||
<start_of_turn>model
|
<start_of_turn>model
|
||||||
{{ .Response }}<end_of_turn>
|
{{ .Response }}<end_of_turn>
|
||||||
|
{{- end }}
|
||||||
@@ -1,3 +1,16 @@
|
|||||||
|
{{- if .Messages }}
|
||||||
|
{{- if .System }}System:
|
||||||
|
{{ .System }}
|
||||||
|
|
||||||
|
{{ end }}
|
||||||
|
{{- range .Messages }}
|
||||||
|
{{- if eq .Role "user" }}Question:
|
||||||
|
{{- else if eq .Role "assistant" }}Answer:
|
||||||
|
{{- end }}
|
||||||
|
{{ .Content }}
|
||||||
|
|
||||||
|
{{ end }}Answer:
|
||||||
|
{{ else }}
|
||||||
{{ if .System }}
|
{{ if .System }}
|
||||||
System:
|
System:
|
||||||
{{ .System }}
|
{{ .System }}
|
||||||
@@ -7,3 +20,4 @@ System:
|
|||||||
|
|
||||||
{{ end }}Answer:
|
{{ end }}Answer:
|
||||||
{{ .Response }}
|
{{ .Response }}
|
||||||
|
{{- end }}
|
||||||
@@ -1,3 +1,16 @@
|
|||||||
|
{{- if .Messages }}
|
||||||
|
{{- range $index, $_ := .Messages }}
|
||||||
|
{{- if eq .Role "user" }}[INST] {{ if eq $index 0 }}<<SYS>>
|
||||||
|
{{- if $.System }}
|
||||||
|
{{ $.System }}
|
||||||
|
{{ end }}<</SYS>>
|
||||||
|
|
||||||
|
{{ end }}{{ .Content }}
|
||||||
|
{{- else }} [/INST] {{ .Content }}</s><s>
|
||||||
|
{{- end }}
|
||||||
|
{{- end }} [/INST]
|
||||||
|
{{- else }}
|
||||||
[INST] <<SYS>>{{ .System }}<</SYS>>
|
[INST] <<SYS>>{{ .System }}<</SYS>>
|
||||||
|
|
||||||
{{ .Prompt }} [/INST] {{ .Response }}
|
{{ .Prompt }} [/INST] {{ .Response }}
|
||||||
|
{{- end }}
|
||||||
@@ -1,3 +1,14 @@
|
|||||||
|
{{- if .Messages }}
|
||||||
|
{{- if .System }}<|start_header_id|>system<|end_header_id|>
|
||||||
|
|
||||||
|
{{ .System }}<|eot_id|>
|
||||||
|
{{- end }}
|
||||||
|
{{- range .Messages }}<|start_header_id|>{{ .Role }}<|end_header_id|>
|
||||||
|
|
||||||
|
{{ .Content }}<|eot_id|>
|
||||||
|
{{- end }}<|start_header_id|>assistant<|end_header_id|>
|
||||||
|
|
||||||
|
{{ else }}
|
||||||
{{ if .System }}<|start_header_id|>system<|end_header_id|>
|
{{ if .System }}<|start_header_id|>system<|end_header_id|>
|
||||||
|
|
||||||
{{ .System }}<|eot_id|>{{ end }}{{ if .Prompt }}<|start_header_id|>user<|end_header_id|>
|
{{ .System }}<|eot_id|>{{ end }}{{ if .Prompt }}<|start_header_id|>user<|end_header_id|>
|
||||||
@@ -5,3 +16,4 @@
|
|||||||
{{ .Prompt }}<|eot_id|>{{ end }}<|start_header_id|>assistant<|end_header_id|>
|
{{ .Prompt }}<|eot_id|>{{ end }}<|start_header_id|>assistant<|end_header_id|>
|
||||||
|
|
||||||
{{ .Response }}<|eot_id|>
|
{{ .Response }}<|eot_id|>
|
||||||
|
{{- end }}
|
||||||
@@ -1,3 +1,15 @@
|
|||||||
|
{{- if .Messages }}
|
||||||
|
{{- if .System }}{{ .System }}
|
||||||
|
|
||||||
|
{{ end }}
|
||||||
|
{{- range .Messages }}
|
||||||
|
{{- if eq .Role "user" }}@@ Instruction
|
||||||
|
{{- else if eq .Role "assistant" }}@@ Response
|
||||||
|
{{- end }}
|
||||||
|
{{ .Content }}
|
||||||
|
|
||||||
|
{{ end }}@@ Response
|
||||||
|
{{ else }}
|
||||||
{{ if .System }}{{ .System }}
|
{{ if .System }}{{ .System }}
|
||||||
|
|
||||||
{{ end }}{{ if .Prompt }}@@ Instruction
|
{{ end }}{{ if .Prompt }}@@ Instruction
|
||||||
@@ -5,3 +17,4 @@
|
|||||||
|
|
||||||
{{ end }}@@ Response
|
{{ end }}@@ Response
|
||||||
{{ .Response }}
|
{{ .Response }}
|
||||||
|
{{- end }}
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
{{ if .System }}<|im_start|>system
|
{{- if .Messages }}
|
||||||
{{ .System }}<|im_end|>
|
{{- range $index, $_ := .Messages }}
|
||||||
{{ end }}{{ if .Prompt }}<|im_start|>user
|
{{- if eq .Role "user" }}[INST] {{ if and $.System (eq (len (slice $.Messages $index)) 1) }}{{ $.System }}
|
||||||
{{ .Prompt }}<|im_end|>
|
{{ end }}{{ .Content }}
|
||||||
{{ end }}<|im_start|>assistant
|
{{- else if eq .Role "assistant" }}[/INST] {{ .Content }}</s>
|
||||||
{{ .Response }}<|im_end|>
|
{{- end }}
|
||||||
|
{{- end }}[/INST]
|
||||||
|
{{- else }}[INST] {{ if .System }}{{ .System }} {{ end }}{{ .Prompt }} [/INST] {{ .Response }}
|
||||||
|
{{- end }}
|
||||||
@@ -1 +1,11 @@
|
|||||||
|
{{- if .Messages }}
|
||||||
|
{{- if .System }}GPT Correct System: {{ .System }}<|end_of_turn|>
|
||||||
|
{{- end }}
|
||||||
|
{{- range .Messages }}GPT Correct
|
||||||
|
{{- if eq .Role "user" }} User:
|
||||||
|
{{- else if eq .Role "assistant" }} Assistant:
|
||||||
|
{{- end }} {{ .Content }}<|end_of_turn|>
|
||||||
|
{{- end }}GPT Correct Assistant:
|
||||||
|
{{- else }}
|
||||||
{{ .System }}<|end_of_turn|>GPT4 Correct User: {{ .Prompt }}<|end_of_turn|>GPT4 Correct Assistant: {{ .Response }}<|end_of_turn|>
|
{{ .System }}<|end_of_turn|>GPT4 Correct User: {{ .Prompt }}<|end_of_turn|>GPT4 Correct Assistant: {{ .Response }}<|end_of_turn|>
|
||||||
|
{{- end }}
|
||||||
@@ -1,6 +1,15 @@
|
|||||||
|
{{- if .Messages }}
|
||||||
|
{{- if .System }}<|system|>
|
||||||
|
{{ .System }}<|end|>
|
||||||
|
{{ end }}
|
||||||
|
{{- range .Messages }}<|{{ .Role }}|>
|
||||||
|
{{ .Content }}<|end|>
|
||||||
|
{{ end }}<|assistant|>
|
||||||
|
{{ else }}
|
||||||
{{ if .System }}<|system|>
|
{{ if .System }}<|system|>
|
||||||
{{ .System }}<|end|>
|
{{ .System }}<|end|>
|
||||||
{{ end }}{{ if .Prompt }}<|user|>
|
{{ end }}{{ if .Prompt }}<|user|>
|
||||||
{{ .Prompt }}<|end|>
|
{{ .Prompt }}<|end|>
|
||||||
{{ end }}<|assistant|>
|
{{ end }}<|assistant|>
|
||||||
{{ .Response }}<|end|>
|
{{ .Response }}<|end|>
|
||||||
|
{{- end }}
|
||||||
@@ -1,3 +1,16 @@
|
|||||||
|
{{- if .Messages }}
|
||||||
|
{{- if .System }}### System:
|
||||||
|
{{ .System }}
|
||||||
|
|
||||||
|
{{ end }}
|
||||||
|
{{- range .Messages }}
|
||||||
|
{{- if eq .Role "user" }}### User:
|
||||||
|
{{ .Content }}
|
||||||
|
{{ else if eq .Role "assistant" }}### Assistant:
|
||||||
|
{{ .Content }}</s>
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}### Assistant:
|
||||||
|
{{ else }}
|
||||||
{{ if .System }}### System:
|
{{ if .System }}### System:
|
||||||
{{ .System }}
|
{{ .System }}
|
||||||
|
|
||||||
@@ -6,3 +19,4 @@
|
|||||||
|
|
||||||
{{ end }}### Assistant:
|
{{ end }}### Assistant:
|
||||||
{{ .Response }}
|
{{ .Response }}
|
||||||
|
{{- end }}
|
||||||
@@ -1,3 +1,17 @@
|
|||||||
|
{{- if .Messages }}
|
||||||
|
{{- if .System }}{{ .System }}
|
||||||
|
|
||||||
|
{{ end }}
|
||||||
|
{{- range .Messages }}
|
||||||
|
{{- if eq .Role "user" }}### Instruction
|
||||||
|
{{ .Content }}
|
||||||
|
|
||||||
|
{{ else if eq .Role "assistant" }}### Response
|
||||||
|
{{ .Content }}<|endoftext|>
|
||||||
|
|
||||||
|
{{ end }}
|
||||||
|
{{- end }}### Response
|
||||||
|
{{ else }}
|
||||||
{{ if .System }}{{ .System }}
|
{{ if .System }}{{ .System }}
|
||||||
|
|
||||||
{{ end }}{{ if .Prompt }}### Instruction
|
{{ end }}{{ if .Prompt }}### Instruction
|
||||||
@@ -7,3 +21,4 @@
|
|||||||
{{ end }}### Response
|
{{ end }}### Response
|
||||||
{{ .Response }}<|endoftext|>
|
{{ .Response }}<|endoftext|>
|
||||||
|
|
||||||
|
{{- end }}
|
||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"embed"
|
"embed"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"math"
|
"math"
|
||||||
"slices"
|
"slices"
|
||||||
@@ -14,6 +15,7 @@ import (
|
|||||||
"text/template/parse"
|
"text/template/parse"
|
||||||
|
|
||||||
"github.com/agnivade/levenshtein"
|
"github.com/agnivade/levenshtein"
|
||||||
|
"github.com/ollama/ollama/api"
|
||||||
"golang.org/x/exp/maps"
|
"golang.org/x/exp/maps"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -74,30 +76,59 @@ func Named(s string) (*named, error) {
|
|||||||
return nil, errors.New("no matching template found")
|
return nil, errors.New("no matching template found")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var DefaultTemplate, _ = Parse("{{ .Prompt }}")
|
||||||
|
|
||||||
type Template struct {
|
type Template struct {
|
||||||
*template.Template
|
*template.Template
|
||||||
raw string
|
raw string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// response is a template node that can be added to templates that don't already have one
|
||||||
|
var response = parse.ActionNode{
|
||||||
|
NodeType: parse.NodeAction,
|
||||||
|
Pipe: &parse.PipeNode{
|
||||||
|
NodeType: parse.NodePipe,
|
||||||
|
Cmds: []*parse.CommandNode{
|
||||||
|
{
|
||||||
|
NodeType: parse.NodeCommand,
|
||||||
|
Args: []parse.Node{
|
||||||
|
&parse.FieldNode{
|
||||||
|
NodeType: parse.NodeField,
|
||||||
|
Ident: []string{"Response"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func Parse(s string) (*Template, error) {
|
||||||
|
tmpl := template.New("").Option("missingkey=zero")
|
||||||
|
|
||||||
|
tmpl, err := tmpl.Parse(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
t := Template{Template: tmpl, raw: s}
|
||||||
|
if vars := t.Vars(); !slices.Contains(vars, "messages") && !slices.Contains(vars, "response") {
|
||||||
|
// touch up the template and append {{ .Response }}
|
||||||
|
tmpl.Tree.Root.Nodes = append(tmpl.Tree.Root.Nodes, &response)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &t, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (t *Template) String() string {
|
func (t *Template) String() string {
|
||||||
return t.raw
|
return t.raw
|
||||||
}
|
}
|
||||||
|
|
||||||
var DefaultTemplate, _ = Parse("{{ .Prompt }}")
|
|
||||||
|
|
||||||
func Parse(s string) (*Template, error) {
|
|
||||||
t, err := template.New("").Option("missingkey=zero").Parse(s)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Template{Template: t, raw: s}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Template) Vars() []string {
|
func (t *Template) Vars() []string {
|
||||||
var vars []string
|
var vars []string
|
||||||
for _, n := range t.Tree.Root.Nodes {
|
for _, tt := range t.Templates() {
|
||||||
vars = append(vars, parseNode(n)...)
|
for _, n := range tt.Root.Nodes {
|
||||||
|
vars = append(vars, parseNode(n)...)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
set := make(map[string]struct{})
|
set := make(map[string]struct{})
|
||||||
@@ -110,6 +141,103 @@ func (t *Template) Vars() []string {
|
|||||||
return vars
|
return vars
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Values struct {
|
||||||
|
Messages []api.Message
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Template) Execute(w io.Writer, v Values) error {
|
||||||
|
system, collated := collate(v.Messages)
|
||||||
|
if slices.Contains(t.Vars(), "messages") {
|
||||||
|
return t.Template.Execute(w, map[string]any{
|
||||||
|
"System": system,
|
||||||
|
"Messages": collated,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var b bytes.Buffer
|
||||||
|
var prompt, response string
|
||||||
|
for i, m := range collated {
|
||||||
|
if m.Role == "user" {
|
||||||
|
prompt = m.Content
|
||||||
|
} else {
|
||||||
|
response = m.Content
|
||||||
|
}
|
||||||
|
|
||||||
|
if i != len(collated)-1 && prompt != "" && response != "" {
|
||||||
|
if err := t.Template.Execute(&b, map[string]any{
|
||||||
|
"System": "",
|
||||||
|
"Prompt": prompt,
|
||||||
|
"Response": response,
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
prompt = ""
|
||||||
|
response = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var cut bool
|
||||||
|
tree := t.Template.Copy()
|
||||||
|
// for the last message, cut everything after "{{ .Response }}"
|
||||||
|
tree.Root.Nodes = slices.DeleteFunc(tree.Root.Nodes, func(n parse.Node) bool {
|
||||||
|
if slices.Contains(parseNode(n), "Response") {
|
||||||
|
cut = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return cut
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := template.Must(template.New("").AddParseTree("", tree)).Execute(&b, map[string]any{
|
||||||
|
"System": system,
|
||||||
|
"Prompt": prompt,
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := io.Copy(w, &b)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
type messages []*api.Message
|
||||||
|
|
||||||
|
// collate messages based on role. consecutive messages of the same role are merged
|
||||||
|
// into a single message. collate also pulls out and merges messages with Role == "system"
|
||||||
|
// which are templated separately. As a side effect, it mangles message content adding image
|
||||||
|
// tags ([img-%d]) as needed
|
||||||
|
func collate(msgs []api.Message) (system string, collated messages) {
|
||||||
|
var n int
|
||||||
|
for i := range msgs {
|
||||||
|
msg := msgs[i]
|
||||||
|
if msg.Role == "system" {
|
||||||
|
if system != "" {
|
||||||
|
system += "\n\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
system += msg.Content
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for range msg.Images {
|
||||||
|
imageTag := fmt.Sprintf("[img-%d]", n)
|
||||||
|
if !strings.Contains(msg.Content, "[img]") {
|
||||||
|
msg.Content = strings.TrimSpace("[img] " + msg.Content)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.Content = strings.Replace(msg.Content, "[img]", imageTag, 1)
|
||||||
|
n++
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(collated) > 0 && collated[len(collated)-1].Role == msg.Role {
|
||||||
|
collated[len(collated)-1].Content += "\n\n" + msg.Content
|
||||||
|
} else {
|
||||||
|
collated = append(collated, &msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func parseNode(n parse.Node) []string {
|
func parseNode(n parse.Node) []string {
|
||||||
switch n := n.(type) {
|
switch n := n.(type) {
|
||||||
case *parse.ActionNode:
|
case *parse.ActionNode:
|
||||||
@@ -152,6 +280,8 @@ func parseNode(n parse.Node) []string {
|
|||||||
return names
|
return names
|
||||||
case *parse.FieldNode:
|
case *parse.FieldNode:
|
||||||
return n.Ident
|
return n.Ident
|
||||||
|
case *parse.TemplateNode:
|
||||||
|
return parseNode(n.Pipe)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -8,9 +8,11 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"slices"
|
"slices"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"text/template"
|
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
"github.com/ollama/ollama/api"
|
||||||
"github.com/ollama/ollama/llm"
|
"github.com/ollama/ollama/llm"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -46,7 +48,7 @@ func TestNamed(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
tmpl, err := template.New(s).Parse(b.String())
|
tmpl, err := Parse(b.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -59,18 +61,81 @@ func TestNamed(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTemplate(t *testing.T) {
|
||||||
|
cases := make(map[string][]api.Message)
|
||||||
|
for _, mm := range [][]api.Message{
|
||||||
|
{
|
||||||
|
{Role: "user", Content: "Hello, how are you?"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{Role: "user", Content: "Hello, how are you?"},
|
||||||
|
{Role: "assistant", Content: "I'm doing great. How can I help you today?"},
|
||||||
|
{Role: "user", Content: "I'd like to show off how chat templating works!"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{Role: "system", Content: "You are a helpful assistant."},
|
||||||
|
{Role: "user", Content: "Hello, how are you?"},
|
||||||
|
{Role: "assistant", Content: "I'm doing great. How can I help you today?"},
|
||||||
|
{Role: "user", Content: "I'd like to show off how chat templating works!"},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
var roles []string
|
||||||
|
for _, m := range mm {
|
||||||
|
roles = append(roles, m.Role)
|
||||||
|
}
|
||||||
|
|
||||||
|
cases[strings.Join(roles, "-")] = mm
|
||||||
|
}
|
||||||
|
|
||||||
|
matches, err := filepath.Glob("*.gotmpl")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, match := range matches {
|
||||||
|
t.Run(match, func(t *testing.T) {
|
||||||
|
bts, err := os.ReadFile(match)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpl, err := Parse(string(bts))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for n, tt := range cases {
|
||||||
|
t.Run(n, func(t *testing.T) {
|
||||||
|
var actual bytes.Buffer
|
||||||
|
if err := tmpl.Execute(&actual, Values{Messages: tt}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expect, err := os.ReadFile(filepath.Join("testdata", match, n))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if diff := cmp.Diff(actual.Bytes(), expect); diff != "" {
|
||||||
|
t.Errorf("mismatch (-got +want):\n%s", diff)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestParse(t *testing.T) {
|
func TestParse(t *testing.T) {
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
template string
|
template string
|
||||||
vars []string
|
vars []string
|
||||||
}{
|
}{
|
||||||
{"{{ .Prompt }}", []string{"prompt"}},
|
{"{{ .Prompt }}", []string{"prompt", "response"}},
|
||||||
{"{{ .System }} {{ .Prompt }}", []string{"prompt", "system"}},
|
{"{{ .System }} {{ .Prompt }}", []string{"prompt", "response", "system"}},
|
||||||
{"{{ .System }} {{ .Prompt }} {{ .Response }}", []string{"prompt", "response", "system"}},
|
{"{{ .System }} {{ .Prompt }} {{ .Response }}", []string{"prompt", "response", "system"}},
|
||||||
{"{{ with .Tools }}{{ . }}{{ end }} {{ .System }} {{ .Prompt }}", []string{"prompt", "system", "tools"}},
|
{"{{ with .Tools }}{{ . }}{{ end }} {{ .System }} {{ .Prompt }}", []string{"prompt", "response", "system", "tools"}},
|
||||||
{"{{ range .Messages }}{{ .Role }} {{ .Content }}{{ end }}", []string{"content", "messages", "role"}},
|
{"{{ range .Messages }}{{ .Role }} {{ .Content }}{{ end }}", []string{"content", "messages", "role"}},
|
||||||
{"{{ range .Messages }}{{ if eq .Role \"system\" }}SYSTEM: {{ .Content }}{{ else if eq .Role \"user\" }}USER: {{ .Content }}{{ else if eq .Role \"assistant\" }}ASSISTANT: {{ .Content }}{{ end }}{{ end }}", []string{"content", "messages", "role"}},
|
{"{{ range .Messages }}{{ if eq .Role \"system\" }}SYSTEM: {{ .Content }}{{ else if eq .Role \"user\" }}USER: {{ .Content }}{{ else if eq .Role \"assistant\" }}ASSISTANT: {{ .Content }}{{ end }}{{ end }}", []string{"content", "messages", "role"}},
|
||||||
{"{{ .Prompt }} {{ .Suffix }}", []string{"prompt", "suffix"}},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range cases {
|
for _, tt := range cases {
|
||||||
@@ -87,3 +152,159 @@ func TestParse(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestExecuteWithMessages(t *testing.T) {
|
||||||
|
type template struct {
|
||||||
|
name string
|
||||||
|
template string
|
||||||
|
}
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
templates []template
|
||||||
|
values Values
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"mistral",
|
||||||
|
[]template{
|
||||||
|
{"no response", `[INST] {{ if .System }}{{ .System }}{{ "\n\n" }}{{ end }}{{ .Prompt }}[/INST] `},
|
||||||
|
{"response", `[INST] {{ if .System }}{{ .System }}{{ "\n\n" }}{{ end }}{{ .Prompt }}[/INST] {{ .Response }}`},
|
||||||
|
{"messages", `{{- range $index, $_ := .Messages }}
|
||||||
|
{{- if eq .Role "user" }}[INST] {{ if and (eq (len (slice $.Messages $index)) 1) $.System }}{{ $.System }}{{ "\n\n" }}
|
||||||
|
{{- end }}{{ .Content }}[/INST] {{ else if eq .Role "assistant" }}{{ .Content }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}`},
|
||||||
|
},
|
||||||
|
Values{
|
||||||
|
Messages: []api.Message{
|
||||||
|
{Role: "user", Content: "Hello friend!"},
|
||||||
|
{Role: "assistant", Content: "Hello human!"},
|
||||||
|
{Role: "user", Content: "What is your name?"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
`[INST] Hello friend![/INST] Hello human![INST] What is your name?[/INST] `,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mistral system",
|
||||||
|
[]template{
|
||||||
|
{"no response", `[INST] {{ if .System }}{{ .System }}{{ "\n\n" }}{{ end }}{{ .Prompt }}[/INST] `},
|
||||||
|
{"response", `[INST] {{ if .System }}{{ .System }}{{ "\n\n" }}{{ end }}{{ .Prompt }}[/INST] {{ .Response }}`},
|
||||||
|
{"messages", `
|
||||||
|
{{- range $index, $_ := .Messages }}
|
||||||
|
{{- if eq .Role "user" }}[INST] {{ if and (eq (len (slice $.Messages $index)) 1) $.System }}{{ $.System }}{{ "\n\n" }}
|
||||||
|
{{- end }}{{ .Content }}[/INST] {{ else if eq .Role "assistant" }}{{ .Content }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}`},
|
||||||
|
},
|
||||||
|
Values{
|
||||||
|
Messages: []api.Message{
|
||||||
|
{Role: "system", Content: "You are a helpful assistant!"},
|
||||||
|
{Role: "user", Content: "Hello friend!"},
|
||||||
|
{Role: "assistant", Content: "Hello human!"},
|
||||||
|
{Role: "user", Content: "What is your name?"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
`[INST] Hello friend![/INST] Hello human![INST] You are a helpful assistant!
|
||||||
|
|
||||||
|
What is your name?[/INST] `,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"chatml",
|
||||||
|
[]template{
|
||||||
|
// this does not have a "no response" test because it's impossible to render the same output
|
||||||
|
{"response", `{{ if .System }}<|im_start|>system
|
||||||
|
{{ .System }}<|im_end|>
|
||||||
|
{{ end }}{{ if .Prompt }}<|im_start|>user
|
||||||
|
{{ .Prompt }}<|im_end|>
|
||||||
|
{{ end }}<|im_start|>assistant
|
||||||
|
{{ .Response }}<|im_end|>
|
||||||
|
`},
|
||||||
|
{"messages", `
|
||||||
|
{{- range $index, $_ := .Messages }}
|
||||||
|
{{- if and (eq .Role "user") (eq (len (slice $.Messages $index)) 1) $.System }}<|im_start|>system
|
||||||
|
{{ $.System }}<|im_end|>{{ "\n" }}
|
||||||
|
{{- end }}<|im_start|>{{ .Role }}
|
||||||
|
{{ .Content }}<|im_end|>{{ "\n" }}
|
||||||
|
{{- end }}<|im_start|>assistant
|
||||||
|
`},
|
||||||
|
},
|
||||||
|
Values{
|
||||||
|
Messages: []api.Message{
|
||||||
|
{Role: "system", Content: "You are a helpful assistant!"},
|
||||||
|
{Role: "user", Content: "Hello friend!"},
|
||||||
|
{Role: "assistant", Content: "Hello human!"},
|
||||||
|
{Role: "user", Content: "What is your name?"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
`<|im_start|>user
|
||||||
|
Hello friend!<|im_end|>
|
||||||
|
<|im_start|>assistant
|
||||||
|
Hello human!<|im_end|>
|
||||||
|
<|im_start|>system
|
||||||
|
You are a helpful assistant!<|im_end|>
|
||||||
|
<|im_start|>user
|
||||||
|
What is your name?<|im_end|>
|
||||||
|
<|im_start|>assistant
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"moondream",
|
||||||
|
[]template{
|
||||||
|
// this does not have a "no response" test because it's impossible to render the same output
|
||||||
|
{"response", `{{ if .Prompt }}Question: {{ .Prompt }}
|
||||||
|
|
||||||
|
{{ end }}Answer: {{ .Response }}
|
||||||
|
|
||||||
|
`},
|
||||||
|
{"messages", `
|
||||||
|
{{- range .Messages }}
|
||||||
|
{{- if eq .Role "user" }}Question: {{ .Content }}{{ "\n\n" }}
|
||||||
|
{{- else if eq .Role "assistant" }}Answer: {{ .Content }}{{ "\n\n" }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}Answer: `},
|
||||||
|
},
|
||||||
|
Values{
|
||||||
|
Messages: []api.Message{
|
||||||
|
{Role: "user", Content: "What's in this image?", Images: []api.ImageData{[]byte("")}},
|
||||||
|
{Role: "assistant", Content: "It's a hot dog."},
|
||||||
|
{Role: "user", Content: "What's in _this_ image?"},
|
||||||
|
{Role: "user", Images: []api.ImageData{[]byte("")}},
|
||||||
|
{Role: "user", Content: "Is it a hot dog?"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
`Question: [img-0] What's in this image?
|
||||||
|
|
||||||
|
Answer: It's a hot dog.
|
||||||
|
|
||||||
|
Question: What's in _this_ image?
|
||||||
|
|
||||||
|
[img-1]
|
||||||
|
|
||||||
|
Is it a hot dog?
|
||||||
|
|
||||||
|
Answer: `,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range cases {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
for _, ttt := range tt.templates {
|
||||||
|
t.Run(ttt.name, func(t *testing.T) {
|
||||||
|
tmpl, err := Parse(ttt.template)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var b bytes.Buffer
|
||||||
|
if err := tmpl.Execute(&b, tt.values); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.String() != tt.expected {
|
||||||
|
t.Errorf("expected\n%s,\ngot\n%s", tt.expected, b.String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
1
template/testdata/alfred.gotmpl/system-user-assistant-user
vendored
Normal file
1
template/testdata/alfred.gotmpl/system-user-assistant-user
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<start_system>You are a helpful assistant.<end_message><start_user>Hello, how are you?<end_message><start_assistant>I'm doing great. How can I help you today?<end_message><start_user>I'd like to show off how chat templating works!<end_message><start_assistant>
|
||||||
1
template/testdata/alfred.gotmpl/user
vendored
Normal file
1
template/testdata/alfred.gotmpl/user
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<start_user>Hello, how are you?<end_message><start_assistant>
|
||||||
1
template/testdata/alfred.gotmpl/user-assistant-user
vendored
Normal file
1
template/testdata/alfred.gotmpl/user-assistant-user
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<start_user>Hello, how are you?<end_message><start_assistant>I'm doing great. How can I help you today?<end_message><start_user>I'd like to show off how chat templating works!<end_message><start_assistant>
|
||||||
10
template/testdata/alpaca.gotmpl/system-user-assistant-user
vendored
Normal file
10
template/testdata/alpaca.gotmpl/system-user-assistant-user
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
You are a helpful assistant.### Instruction:
|
||||||
|
Hello, how are you?
|
||||||
|
|
||||||
|
### Response:
|
||||||
|
I'm doing great. How can I help you today?
|
||||||
|
|
||||||
|
### Instruction:
|
||||||
|
I'd like to show off how chat templating works!
|
||||||
|
|
||||||
|
### Response:
|
||||||
4
template/testdata/alpaca.gotmpl/user
vendored
Normal file
4
template/testdata/alpaca.gotmpl/user
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
### Instruction:
|
||||||
|
Hello, how are you?
|
||||||
|
|
||||||
|
### Response:
|
||||||
10
template/testdata/alpaca.gotmpl/user-assistant-user
vendored
Normal file
10
template/testdata/alpaca.gotmpl/user-assistant-user
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
### Instruction:
|
||||||
|
Hello, how are you?
|
||||||
|
|
||||||
|
### Response:
|
||||||
|
I'm doing great. How can I help you today?
|
||||||
|
|
||||||
|
### Instruction:
|
||||||
|
I'd like to show off how chat templating works!
|
||||||
|
|
||||||
|
### Response:
|
||||||
9
template/testdata/chatml.gotmpl/system-user-assistant-user
vendored
Normal file
9
template/testdata/chatml.gotmpl/system-user-assistant-user
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<|im_start|>system
|
||||||
|
You are a helpful assistant.<|im_end|>
|
||||||
|
<|im_start|>user
|
||||||
|
Hello, how are you?<|im_end|>
|
||||||
|
<|im_start|>assistant
|
||||||
|
I'm doing great. How can I help you today?<|im_end|>
|
||||||
|
<|im_start|>user
|
||||||
|
I'd like to show off how chat templating works!<|im_end|>
|
||||||
|
<|im_start|>assistant
|
||||||
3
template/testdata/chatml.gotmpl/user
vendored
Normal file
3
template/testdata/chatml.gotmpl/user
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<|im_start|>user
|
||||||
|
Hello, how are you?<|im_end|>
|
||||||
|
<|im_start|>assistant
|
||||||
7
template/testdata/chatml.gotmpl/user-assistant-user
vendored
Normal file
7
template/testdata/chatml.gotmpl/user-assistant-user
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<|im_start|>user
|
||||||
|
Hello, how are you?<|im_end|>
|
||||||
|
<|im_start|>assistant
|
||||||
|
I'm doing great. How can I help you today?<|im_end|>
|
||||||
|
<|im_start|>user
|
||||||
|
I'd like to show off how chat templating works!<|im_end|>
|
||||||
|
<|im_start|>assistant
|
||||||
9
template/testdata/chatqa.gotmpl/system-user-assistant-user
vendored
Normal file
9
template/testdata/chatqa.gotmpl/system-user-assistant-user
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
System: You are a helpful assistant.
|
||||||
|
|
||||||
|
User: Hello, how are you?
|
||||||
|
|
||||||
|
Assistant: I'm doing great. How can I help you today?
|
||||||
|
|
||||||
|
User: I'd like to show off how chat templating works!
|
||||||
|
|
||||||
|
Assistant:
|
||||||
3
template/testdata/chatqa.gotmpl/user
vendored
Normal file
3
template/testdata/chatqa.gotmpl/user
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
User: Hello, how are you?
|
||||||
|
|
||||||
|
Assistant:
|
||||||
7
template/testdata/chatqa.gotmpl/user-assistant-user
vendored
Normal file
7
template/testdata/chatqa.gotmpl/user-assistant-user
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
User: Hello, how are you?
|
||||||
|
|
||||||
|
Assistant: I'm doing great. How can I help you today?
|
||||||
|
|
||||||
|
User: I'd like to show off how chat templating works!
|
||||||
|
|
||||||
|
Assistant:
|
||||||
11
template/testdata/codellama-70b-instruct.gotmpl/system-user-assistant-user
vendored
Normal file
11
template/testdata/codellama-70b-instruct.gotmpl/system-user-assistant-user
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
Source: system
|
||||||
|
|
||||||
|
You are a helpful assistant. <step> Source: user
|
||||||
|
|
||||||
|
Hello, how are you? <step> Source: assistant
|
||||||
|
|
||||||
|
I'm doing great. How can I help you today? <step> Source: user
|
||||||
|
|
||||||
|
I'd like to show off how chat templating works! <step> Source: assistant
|
||||||
|
Destination: user
|
||||||
|
|
||||||
5
template/testdata/codellama-70b-instruct.gotmpl/user
vendored
Normal file
5
template/testdata/codellama-70b-instruct.gotmpl/user
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
Source: user
|
||||||
|
|
||||||
|
Hello, how are you? <step> Source: assistant
|
||||||
|
Destination: user
|
||||||
|
|
||||||
9
template/testdata/codellama-70b-instruct.gotmpl/user-assistant-user
vendored
Normal file
9
template/testdata/codellama-70b-instruct.gotmpl/user-assistant-user
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
Source: user
|
||||||
|
|
||||||
|
Hello, how are you? <step> Source: assistant
|
||||||
|
|
||||||
|
I'm doing great. How can I help you today? <step> Source: user
|
||||||
|
|
||||||
|
I'd like to show off how chat templating works! <step> Source: assistant
|
||||||
|
Destination: user
|
||||||
|
|
||||||
8
template/testdata/falcon-instruct.gotmpl/system-user-assistant-user
vendored
Normal file
8
template/testdata/falcon-instruct.gotmpl/system-user-assistant-user
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
System: You are a helpful assistant.
|
||||||
|
User:
|
||||||
|
Hello, how are you?
|
||||||
|
Falcon:
|
||||||
|
I'm doing great. How can I help you today?
|
||||||
|
User:
|
||||||
|
I'd like to show off how chat templating works!
|
||||||
|
Falcon:
|
||||||
3
template/testdata/falcon-instruct.gotmpl/user
vendored
Normal file
3
template/testdata/falcon-instruct.gotmpl/user
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
User:
|
||||||
|
Hello, how are you?
|
||||||
|
Falcon:
|
||||||
7
template/testdata/falcon-instruct.gotmpl/user-assistant-user
vendored
Normal file
7
template/testdata/falcon-instruct.gotmpl/user-assistant-user
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
User:
|
||||||
|
Hello, how are you?
|
||||||
|
Falcon:
|
||||||
|
I'm doing great. How can I help you today?
|
||||||
|
User:
|
||||||
|
I'd like to show off how chat templating works!
|
||||||
|
Falcon:
|
||||||
8
template/testdata/gemma-instruct.gotmpl/system-user-assistant-user
vendored
Normal file
8
template/testdata/gemma-instruct.gotmpl/system-user-assistant-user
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<start_of_turn>user
|
||||||
|
You are a helpful assistant.
|
||||||
|
Hello, how are you?<end_of_turn>
|
||||||
|
<start_of_turn>model
|
||||||
|
I'm doing great. How can I help you today?<end_of_turn>
|
||||||
|
<start_of_turn>user
|
||||||
|
I'd like to show off how chat templating works!<end_of_turn>
|
||||||
|
<start_of_turn>model
|
||||||
3
template/testdata/gemma-instruct.gotmpl/user
vendored
Normal file
3
template/testdata/gemma-instruct.gotmpl/user
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<start_of_turn>user
|
||||||
|
Hello, how are you?<end_of_turn>
|
||||||
|
<start_of_turn>model
|
||||||
7
template/testdata/gemma-instruct.gotmpl/user-assistant-user
vendored
Normal file
7
template/testdata/gemma-instruct.gotmpl/user-assistant-user
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<start_of_turn>user
|
||||||
|
Hello, how are you?<end_of_turn>
|
||||||
|
<start_of_turn>model
|
||||||
|
I'm doing great. How can I help you today?<end_of_turn>
|
||||||
|
<start_of_turn>user
|
||||||
|
I'd like to show off how chat templating works!<end_of_turn>
|
||||||
|
<start_of_turn>model
|
||||||
13
template/testdata/granite-instruct.gotmpl/system-user-assistant-user
vendored
Normal file
13
template/testdata/granite-instruct.gotmpl/system-user-assistant-user
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
System:
|
||||||
|
You are a helpful assistant.
|
||||||
|
|
||||||
|
Question:
|
||||||
|
Hello, how are you?
|
||||||
|
|
||||||
|
Answer:
|
||||||
|
I'm doing great. How can I help you today?
|
||||||
|
|
||||||
|
Question:
|
||||||
|
I'd like to show off how chat templating works!
|
||||||
|
|
||||||
|
Answer:
|
||||||
4
template/testdata/granite-instruct.gotmpl/user
vendored
Normal file
4
template/testdata/granite-instruct.gotmpl/user
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
Question:
|
||||||
|
Hello, how are you?
|
||||||
|
|
||||||
|
Answer:
|
||||||
10
template/testdata/granite-instruct.gotmpl/user-assistant-user
vendored
Normal file
10
template/testdata/granite-instruct.gotmpl/user-assistant-user
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
Question:
|
||||||
|
Hello, how are you?
|
||||||
|
|
||||||
|
Answer:
|
||||||
|
I'm doing great. How can I help you today?
|
||||||
|
|
||||||
|
Question:
|
||||||
|
I'd like to show off how chat templating works!
|
||||||
|
|
||||||
|
Answer:
|
||||||
5
template/testdata/llama2-chat.gotmpl/system-user-assistant-user
vendored
Normal file
5
template/testdata/llama2-chat.gotmpl/system-user-assistant-user
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
[INST] <<SYS>>
|
||||||
|
You are a helpful assistant.
|
||||||
|
<</SYS>>
|
||||||
|
|
||||||
|
Hello, how are you? [/INST] I'm doing great. How can I help you today?</s><s>[INST] I'd like to show off how chat templating works! [/INST]
|
||||||
3
template/testdata/llama2-chat.gotmpl/user
vendored
Normal file
3
template/testdata/llama2-chat.gotmpl/user
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[INST] <<SYS>><</SYS>>
|
||||||
|
|
||||||
|
Hello, how are you? [/INST]
|
||||||
3
template/testdata/llama2-chat.gotmpl/user-assistant-user
vendored
Normal file
3
template/testdata/llama2-chat.gotmpl/user-assistant-user
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[INST] <<SYS>><</SYS>>
|
||||||
|
|
||||||
|
Hello, how are you? [/INST] I'm doing great. How can I help you today?</s><s>[INST] I'd like to show off how chat templating works! [/INST]
|
||||||
10
template/testdata/llama3-instruct.gotmpl/system-user-assistant-user
vendored
Normal file
10
template/testdata/llama3-instruct.gotmpl/system-user-assistant-user
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<|start_header_id|>system<|end_header_id|>
|
||||||
|
|
||||||
|
You are a helpful assistant.<|eot_id|><|start_header_id|>user<|end_header_id|>
|
||||||
|
|
||||||
|
Hello, how are you?<|eot_id|><|start_header_id|>assistant<|end_header_id|>
|
||||||
|
|
||||||
|
I'm doing great. How can I help you today?<|eot_id|><|start_header_id|>user<|end_header_id|>
|
||||||
|
|
||||||
|
I'd like to show off how chat templating works!<|eot_id|><|start_header_id|>assistant<|end_header_id|>
|
||||||
|
|
||||||
4
template/testdata/llama3-instruct.gotmpl/user
vendored
Normal file
4
template/testdata/llama3-instruct.gotmpl/user
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<|start_header_id|>user<|end_header_id|>
|
||||||
|
|
||||||
|
Hello, how are you?<|eot_id|><|start_header_id|>assistant<|end_header_id|>
|
||||||
|
|
||||||
8
template/testdata/llama3-instruct.gotmpl/user-assistant-user
vendored
Normal file
8
template/testdata/llama3-instruct.gotmpl/user-assistant-user
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<|start_header_id|>user<|end_header_id|>
|
||||||
|
|
||||||
|
Hello, how are you?<|eot_id|><|start_header_id|>assistant<|end_header_id|>
|
||||||
|
|
||||||
|
I'm doing great. How can I help you today?<|eot_id|><|start_header_id|>user<|end_header_id|>
|
||||||
|
|
||||||
|
I'd like to show off how chat templating works!<|eot_id|><|start_header_id|>assistant<|end_header_id|>
|
||||||
|
|
||||||
12
template/testdata/magicoder.gotmpl/system-user-assistant-user
vendored
Normal file
12
template/testdata/magicoder.gotmpl/system-user-assistant-user
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
You are a helpful assistant.
|
||||||
|
|
||||||
|
@@ Instruction
|
||||||
|
Hello, how are you?
|
||||||
|
|
||||||
|
@@ Response
|
||||||
|
I'm doing great. How can I help you today?
|
||||||
|
|
||||||
|
@@ Instruction
|
||||||
|
I'd like to show off how chat templating works!
|
||||||
|
|
||||||
|
@@ Response
|
||||||
4
template/testdata/magicoder.gotmpl/user
vendored
Normal file
4
template/testdata/magicoder.gotmpl/user
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
@@ Instruction
|
||||||
|
Hello, how are you?
|
||||||
|
|
||||||
|
@@ Response
|
||||||
10
template/testdata/magicoder.gotmpl/user-assistant-user
vendored
Normal file
10
template/testdata/magicoder.gotmpl/user-assistant-user
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
@@ Instruction
|
||||||
|
Hello, how are you?
|
||||||
|
|
||||||
|
@@ Response
|
||||||
|
I'm doing great. How can I help you today?
|
||||||
|
|
||||||
|
@@ Instruction
|
||||||
|
I'd like to show off how chat templating works!
|
||||||
|
|
||||||
|
@@ Response
|
||||||
2
template/testdata/mistral-instruct.gotmpl/system-user-assistant-user
vendored
Normal file
2
template/testdata/mistral-instruct.gotmpl/system-user-assistant-user
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
[INST] Hello, how are you?[/INST] I'm doing great. How can I help you today?</s>[INST] You are a helpful assistant.
|
||||||
|
I'd like to show off how chat templating works![/INST]
|
||||||
1
template/testdata/mistral-instruct.gotmpl/user
vendored
Normal file
1
template/testdata/mistral-instruct.gotmpl/user
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
[INST] Hello, how are you?[/INST]
|
||||||
1
template/testdata/mistral-instruct.gotmpl/user-assistant-user
vendored
Normal file
1
template/testdata/mistral-instruct.gotmpl/user-assistant-user
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
[INST] Hello, how are you?[/INST] I'm doing great. How can I help you today?</s>[INST] I'd like to show off how chat templating works![/INST]
|
||||||
1
template/testdata/openchat.gotmpl/system-user-assistant-user
vendored
Normal file
1
template/testdata/openchat.gotmpl/system-user-assistant-user
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
GPT Correct System: You are a helpful assistant.<|end_of_turn|>GPT Correct User: Hello, how are you?<|end_of_turn|>GPT Correct Assistant: I'm doing great. How can I help you today?<|end_of_turn|>GPT Correct User: I'd like to show off how chat templating works!<|end_of_turn|>GPT Correct Assistant:
|
||||||
1
template/testdata/openchat.gotmpl/user
vendored
Normal file
1
template/testdata/openchat.gotmpl/user
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
GPT Correct User: Hello, how are you?<|end_of_turn|>GPT Correct Assistant:
|
||||||
1
template/testdata/openchat.gotmpl/user-assistant-user
vendored
Normal file
1
template/testdata/openchat.gotmpl/user-assistant-user
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
GPT Correct User: Hello, how are you?<|end_of_turn|>GPT Correct Assistant: I'm doing great. How can I help you today?<|end_of_turn|>GPT Correct User: I'd like to show off how chat templating works!<|end_of_turn|>GPT Correct Assistant:
|
||||||
9
template/testdata/phi-3.gotmpl/system-user-assistant-user
vendored
Normal file
9
template/testdata/phi-3.gotmpl/system-user-assistant-user
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<|system|>
|
||||||
|
You are a helpful assistant.<|end|>
|
||||||
|
<|user|>
|
||||||
|
Hello, how are you?<|end|>
|
||||||
|
<|assistant|>
|
||||||
|
I'm doing great. How can I help you today?<|end|>
|
||||||
|
<|user|>
|
||||||
|
I'd like to show off how chat templating works!<|end|>
|
||||||
|
<|assistant|>
|
||||||
3
template/testdata/phi-3.gotmpl/user
vendored
Normal file
3
template/testdata/phi-3.gotmpl/user
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<|user|>
|
||||||
|
Hello, how are you?<|end|>
|
||||||
|
<|assistant|>
|
||||||
7
template/testdata/phi-3.gotmpl/user-assistant-user
vendored
Normal file
7
template/testdata/phi-3.gotmpl/user-assistant-user
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<|user|>
|
||||||
|
Hello, how are you?<|end|>
|
||||||
|
<|assistant|>
|
||||||
|
I'm doing great. How can I help you today?<|end|>
|
||||||
|
<|user|>
|
||||||
|
I'd like to show off how chat templating works!<|end|>
|
||||||
|
<|assistant|>
|
||||||
13
template/testdata/solar-instruct.gotmpl/system-user-assistant-user
vendored
Normal file
13
template/testdata/solar-instruct.gotmpl/system-user-assistant-user
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
### System:
|
||||||
|
You are a helpful assistant.
|
||||||
|
|
||||||
|
### User:
|
||||||
|
Hello, how are you?
|
||||||
|
|
||||||
|
### Assistant:
|
||||||
|
I'm doing great. How can I help you today?</s>
|
||||||
|
|
||||||
|
### User:
|
||||||
|
I'd like to show off how chat templating works!
|
||||||
|
|
||||||
|
### Assistant:
|
||||||
4
template/testdata/solar-instruct.gotmpl/user
vendored
Normal file
4
template/testdata/solar-instruct.gotmpl/user
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
### User:
|
||||||
|
Hello, how are you?
|
||||||
|
|
||||||
|
### Assistant:
|
||||||
10
template/testdata/solar-instruct.gotmpl/user-assistant-user
vendored
Normal file
10
template/testdata/solar-instruct.gotmpl/user-assistant-user
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
### User:
|
||||||
|
Hello, how are you?
|
||||||
|
|
||||||
|
### Assistant:
|
||||||
|
I'm doing great. How can I help you today?</s>
|
||||||
|
|
||||||
|
### User:
|
||||||
|
I'd like to show off how chat templating works!
|
||||||
|
|
||||||
|
### Assistant:
|
||||||
12
template/testdata/starcoder2-instruct.gotmpl/system-user-assistant-user
vendored
Normal file
12
template/testdata/starcoder2-instruct.gotmpl/system-user-assistant-user
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
You are a helpful assistant.
|
||||||
|
|
||||||
|
### Instruction
|
||||||
|
Hello, how are you?
|
||||||
|
|
||||||
|
### Response
|
||||||
|
I'm doing great. How can I help you today?<|endoftext|>
|
||||||
|
|
||||||
|
### Instruction
|
||||||
|
I'd like to show off how chat templating works!
|
||||||
|
|
||||||
|
### Response
|
||||||
4
template/testdata/starcoder2-instruct.gotmpl/user
vendored
Normal file
4
template/testdata/starcoder2-instruct.gotmpl/user
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
### Instruction
|
||||||
|
Hello, how are you?
|
||||||
|
|
||||||
|
### Response
|
||||||
10
template/testdata/starcoder2-instruct.gotmpl/user-assistant-user
vendored
Normal file
10
template/testdata/starcoder2-instruct.gotmpl/user-assistant-user
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
### Instruction
|
||||||
|
Hello, how are you?
|
||||||
|
|
||||||
|
### Response
|
||||||
|
I'm doing great. How can I help you today?<|endoftext|>
|
||||||
|
|
||||||
|
### Instruction
|
||||||
|
I'd like to show off how chat templating works!
|
||||||
|
|
||||||
|
### Response
|
||||||
6
template/testdata/vicuna.gotmpl/system-user-assistant-user
vendored
Normal file
6
template/testdata/vicuna.gotmpl/system-user-assistant-user
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
You are a helpful assistant.
|
||||||
|
|
||||||
|
USER: Hello, how are you?
|
||||||
|
ASSISTANT: I'm doing great. How can I help you today?</s>
|
||||||
|
USER: I'd like to show off how chat templating works!
|
||||||
|
ASSISTANT:
|
||||||
2
template/testdata/vicuna.gotmpl/user
vendored
Normal file
2
template/testdata/vicuna.gotmpl/user
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
USER: Hello, how are you?
|
||||||
|
ASSISTANT:
|
||||||
4
template/testdata/vicuna.gotmpl/user-assistant-user
vendored
Normal file
4
template/testdata/vicuna.gotmpl/user-assistant-user
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
USER: Hello, how are you?
|
||||||
|
ASSISTANT: I'm doing great. How can I help you today?</s>
|
||||||
|
USER: I'd like to show off how chat templating works!
|
||||||
|
ASSISTANT:
|
||||||
9
template/testdata/zephyr.gotmpl/system-user-assistant-user
vendored
Normal file
9
template/testdata/zephyr.gotmpl/system-user-assistant-user
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<|system|>
|
||||||
|
You are a helpful assistant.</s>
|
||||||
|
<|user|>
|
||||||
|
Hello, how are you?</s>
|
||||||
|
<|assistant|>
|
||||||
|
I'm doing great. How can I help you today?</s>
|
||||||
|
<|user|>
|
||||||
|
I'd like to show off how chat templating works!</s>
|
||||||
|
<|assistant|>
|
||||||
3
template/testdata/zephyr.gotmpl/user
vendored
Normal file
3
template/testdata/zephyr.gotmpl/user
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<|user|>
|
||||||
|
Hello, how are you?</s>
|
||||||
|
<|assistant|>
|
||||||
7
template/testdata/zephyr.gotmpl/user-assistant-user
vendored
Normal file
7
template/testdata/zephyr.gotmpl/user-assistant-user
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<|user|>
|
||||||
|
Hello, how are you?</s>
|
||||||
|
<|assistant|>
|
||||||
|
I'm doing great. How can I help you today?</s>
|
||||||
|
<|user|>
|
||||||
|
I'd like to show off how chat templating works!</s>
|
||||||
|
<|assistant|>
|
||||||
@@ -1,3 +1,14 @@
|
|||||||
|
{{- if .Messages }}
|
||||||
|
{{- if .System }}{{ .System }}
|
||||||
|
|
||||||
|
{{ end }}
|
||||||
|
{{- range .Messages }}
|
||||||
|
{{- if eq .Role "user" }}USER: {{ .Content }}
|
||||||
|
{{ else if eq .Role "assistant" }}ASSISTANT: {{ .Content }}</s>
|
||||||
|
{{ end }}
|
||||||
|
{{- end }}ASSISTANT:
|
||||||
|
{{- else }}
|
||||||
{{ if .System }}{{ .System }}
|
{{ if .System }}{{ .System }}
|
||||||
{{ end }}{{ if .Prompt }}USER: {{ .Prompt }}
|
{{ end }}{{ if .Prompt }}USER: {{ .Prompt }}
|
||||||
{{ end }}ASSISTANT: {{ .Response }}
|
{{ end }}ASSISTANT: {{ .Response }}
|
||||||
|
{{- end }}
|
||||||
@@ -1,6 +1,15 @@
|
|||||||
|
{{- if .Messages }}
|
||||||
|
{{- if .System }}<|system|>
|
||||||
|
{{ .System }}</s>
|
||||||
|
{{ end }}
|
||||||
|
{{- range .Messages }}<|{{ .Role }}|>
|
||||||
|
{{ .Content }}</s>
|
||||||
|
{{ end }}<|assistant|>
|
||||||
|
{{ else }}
|
||||||
{{ if .System }}<|system|>
|
{{ if .System }}<|system|>
|
||||||
{{ .System }}</s>
|
{{ .System }}</s>
|
||||||
{{ end }}{{ if .Prompt }}<|user|>
|
{{ end }}{{ if .Prompt }}<|user|>
|
||||||
{{ .Prompt }}</s>
|
{{ .Prompt }}</s>
|
||||||
{{ end }}<|assistant|>
|
{{ end }}<|assistant|>
|
||||||
{{ .Response }}</s>
|
{{ .Response }}</s>
|
||||||
|
{{- end }}
|
||||||
Reference in New Issue
Block a user