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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions docs/reference/logits_processors.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Logits Processors

TODO: Explanation of what logits processors do

TODO: List of `OutlinesLogitsProcessor`

TODO: Example of how to implement TemperatureLogitsProcessor

TODO: using logits processors with models directly vs using with outlines

TODO: Explanation of pipelines

TODO: Link to log logits

## Using Logits Processors in Outlines

TODO Explanation

```
import outlines
```


## Chaining Logits Processors

```
import outlines
import outlines.processors as processors

model = outlines.models.llamacpp(
repo_id="M4-ai/TinyMistral-248M-v2-Instruct-GGUF",
filename="TinyMistral-248M-v2-Instruct.Q4_K_M.gguf"
)
Comment on lines +30 to +33

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't seem to work with llamacpp, but it does work with transformers:

import outlines
import outlines.processors as processors
model = outlines.models.transformers(
    "openaccess-ai-collective/tiny-mistral",
)
# Create a chained logits processor
logits_processor = (
    processors.sequence_logging(model.tokenizer) |  # Log the generated sequence
	processors.logits_logging(model.tokenizer) |  # Log the raw logits
	processors.regex(r"[0-9]*", model.tokenizer) |  # Restrict the logits to match the pattern
	processors.temperature(0.5) |  # Set temperature to 0.5
	processors.logits_logging(model.tokenizer)  # Log the restricted, temperature-augmentent, sampled logits
)
generator = outlines.generate.base(model, logits_processor)
generator("What is your favorite number? ")

The error was

Traceback (most recent call last):
  File "/home/cameron/dottxt/outlines/demo-logging.py", line 16, in <module>
    generator("What is your favorite number? ")
  File "/home/cameron/dottxt/outlines/outlines/generate/api.py", line 503, in __call__
    completions = self.model.generate(
  File "/home/cameron/dottxt/outlines/outlines/models/llamacpp.py", line 288, in generate
    completion = self.model(prompts, **llama_cpp_params)
  File "/home/cameron/dottxt/outlines/.venv/lib/python3.10/site-packages/llama_cpp/llama.py", line 1799, in __call__
    return self.create_completion(
  File "/home/cameron/dottxt/outlines/.venv/lib/python3.10/site-packages/llama_cpp/llama.py", line 1732, in create_completion
    completion: Completion = next(completion_or_chunks)  # type: ignore
  File "/home/cameron/dottxt/outlines/.venv/lib/python3.10/site-packages/llama_cpp/llama.py", line 1216, in _create_completion
    for token in self.generate(
  File "/home/cameron/dottxt/outlines/.venv/lib/python3.10/site-packages/llama_cpp/llama.py", line 810, in generate
    token = self.sample(
  File "/home/cameron/dottxt/outlines/.venv/lib/python3.10/site-packages/llama_cpp/llama.py", line 704, in sample
    else logits_processor(self._input_ids[: idx + 1], logits)
  File "/home/cameron/dottxt/outlines/.venv/lib/python3.10/site-packages/llama_cpp/llama.py", line 2250, in __call__
    scores = processor(input_ids, scores)
  File "/home/cameron/dottxt/outlines/.venv/lib/python3.10/site-packages/torch/utils/_contextlib.py", line 116, in decorate_context
    return func(*args, **kwargs)
  File "/home/cameron/dottxt/outlines/outlines/processors/base_logits_processor.py", line 82, in __call__
    processed_logits = self.process_logits(
  File "/home/cameron/dottxt/outlines/outlines/processors/base_logits_processor.py", line 168, in process_logits
    result = processor.process_logits(input_ids, result)
  File "/home/cameron/dottxt/outlines/outlines/processors/logging.py", line 63, in process_logits
    self.logger.info(self.tokenizer.decode(input_ids))
  File "/home/cameron/dottxt/outlines/outlines/models/llamacpp.py", line 56, in decode
    decoded_bytes = self.tokenizer.detokenize(token_ids)
  File "/home/cameron/dottxt/outlines/.venv/lib/python3.10/site-packages/llama_cpp/llama_tokenizer.py", line 52, in detokenize
    return self._model.detokenize(tokens)
  File "/home/cameron/dottxt/outlines/.venv/lib/python3.10/site-packages/llama_cpp/_internals.py", line 224, in detokenize
    self.model, llama_cpp.llama_token(token), buffer, size, 0, special
TypeError: 'list' object cannot be interpreted as an integer


# Create a chained logits processor
logits_processor = (
processors.sequence_logging(model.tokenizer) | # Log the generated sequence
processors.logits_logging(model.tokenizer) | # Log the raw logits
processors.regex(r"[0-9]*", model.tokenizer) | # Restrict the logits to match the pattern
processors.temperature(0.5) | # Set temperature to 0.5
processors.logits_logging(model.tokenizer) # Log the restricted, temperature-augmentent, sampled logits
)

generator = outlines.generate.base(model, logits_process)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
generator = outlines.generate.base(model, logits_process)
generator = outlines.generate.text(model, logits_processor)
  • should be logits_processor
  • Is base defined here? I haven't been able to find it (yet)

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

generator("What is your favorite number? ")
```

Output:
```
TODO
```
22 changes: 22 additions & 0 deletions outlines/generate/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from functools import singledispatch

from outlines.generate.api import SequenceGeneratorAdapter
from outlines.models import MLXLM, OpenAI
from outlines.processors import OutlinesLogitsProcessor
from outlines.samplers import Sampler, multinomial


@singledispatch
def base(
model, logits_processor: OutlinesLogitsProcessor, sampler: Sampler = multinomial()
):
return SequenceGeneratorAdapter(model, logits_processor, sampler)


@base.register(OpenAI)
def base_openai(
model: OpenAI,
logits_processor: OutlinesLogitsProcessor,
sampler: Sampler = multinomial(),
) -> Exception:
raise NotImplementedError("The OpenAI API does not support logits processing.")
29 changes: 28 additions & 1 deletion outlines/processors/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,34 @@
from .base_logits_processor import ChainedLogitsProcessor, OutlinesLogitsProcessor
from .logging import LogitsLoggingLogitsProcessor, SequenceLoggingLogitsProcessor
from .sampling import (
FrequencyPenaltyLogitsProcessor,
MinPLogitsProcessor,
NoRepeatNGramLogitsProcessor,
PresencePenaltyLogitsProcessor,
QuadraticSmoothingLogitsProcessor,
RepetitionPenaltyLogitsProcessor,
TemperatureLogitsProcessor,
TFSLogitsProcessor,
TopKLogitsProcessor,
TopPLogitsProcessor,
)
from .structured import (
CFGLogitsProcessor,
GuideLogitsProcessor,
JSONLogitsProcessor,
OutlinesLogitsProcessor,
RegexLogitsProcessor,
)

# aliases for convenience
chained = ChainedLogitsProcessor

cfg = CFGLogitsProcessor
guide = GuideLogitsProcessor
json = JSONLogitsProcessor
regex = RegexLogitsProcessor

temperature = TemperatureLogitsProcessor
min_p = MinPLogitsProcessor

sequence_logging = SequenceLoggingLogitsProcessor
logits_logging = LogitsLoggingLogitsProcessor
36 changes: 35 additions & 1 deletion outlines/processors/base_logits_processor.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import reprlib
from abc import abstractmethod
from typing import TYPE_CHECKING, List, Protocol, Type, Union
from typing import TYPE_CHECKING, List, Protocol, Type, Union, runtime_checkable

import numpy as np
import torch
Expand All @@ -20,6 +21,7 @@ def is_mlx_array_type(array_type):
return issubclass(array_type, mx.array)


@runtime_checkable
class OutlinesLogitsProcessor(Protocol):
"""
Base class for logits processors which normalizes types of logits:
Expand Down Expand Up @@ -133,3 +135,35 @@ def _from_torch(tensor: torch.Tensor, target_type: Type) -> Array:
raise TypeError(
f"Failed to convert torch tensors to target_type `{target_type}`"
)

def __repr__(self):
return f"{self.__class__.__name__}({reprlib.repr(self.__dict__)})"

def __or__(self, other):
if not isinstance(other, OutlinesLogitsProcessor):
raise ValueError(
"Can only chain with another OutlinesLogitsProcessor instance."
)
return ChainedLogitsProcessor([self, other])


class ChainedLogitsProcessor(OutlinesLogitsProcessor):
"""Handle chaining two logits processors to process logits sequentially"""

processors: List[OutlinesLogitsProcessor]

def __init__(self, processors: List[OutlinesLogitsProcessor]):
self.processors = []
for processor in processors:
if isinstance(processor, ChainedLogitsProcessor):
self.processors.extend(processor.processors)
else:
self.processors.append(processor)

def process_logits(
self, input_ids: List[List[int]], logits: torch.Tensor
) -> torch.Tensor:
result = logits
for processor in self.processors:
result = processor.process_logits(input_ids, result)
return result
64 changes: 64 additions & 0 deletions outlines/processors/logging.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import logging
import sys
import warnings
from typing import List

import torch

from .base_logits_processor import OutlinesLogitsProcessor


class LogitsLoggingLogitsProcessor(OutlinesLogitsProcessor):
"""Handle chaining two logits processors to process logits sequentially"""

def __init__(self, tokenizer, top_n=8, logger=None, warn=True):
self.tokenizer = tokenizer
self.top_n = top_n
if logger is not None:
self.logger = logger
else:
self.logger = logging.getLogger("logits_logger")
self.logger.setLevel(logging.info)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this should be

Suggested change
self.logger.setLevel(logging.info)
self.logger.setLevel(logging.INFO)

self.logger.addHandler(logging.StreamHandler(sys.stderr))
if warn:
warnings.warn(
"Do not use LoggingLogitsProcessor in production, it slows down generation."
)

def process_logits(
self, input_ids: List[List[int]], logits: torch.Tensor
) -> torch.Tensor:
# all token probs for the current batch
probs = torch.nn.functional.softmax(logits, dim=-1)
# top candidate tokens probs
top_indices = torch.topk(probs, self.top_n).indices
top_indices = [
set(row.tolist()) | {self.tokenizer.eos_token_id} for row in top_indices
]
batch_top_probs = [
{token_idx: probs[batch_num, token_idx] for token_idx in row_indices}
for batch_num, row_indices in enumerate(top_indices)
]
self.logger.info(batch_top_probs)
return logits


class SequenceLoggingLogitsProcessor(OutlinesLogitsProcessor):
def __init__(self, tokenizer, top_n=8, logger=None, warn=True):
self.tokenizer = tokenizer
if logger is not None:
self.logger = logger
else:
self.logger = logging.getLogger("sequence_logger")
self.logger.setLevel(logging.info)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
self.logger.setLevel(logging.info)
self.logger.setLevel(logging.INFO)

self.logger.addHandler(logging.StreamHandler(sys.stderr))
if warn:
warnings.warn(
"Do not use SequenceLoggingProcessor in production, it slows down generation."
)

def process_logits(
self, input_ids: List[List[int]], logits: torch.Tensor
) -> torch.Tensor:
self.logger.info(self.tokenizer.decode(input_ids))
return logits
Loading