Compare commits
3 Commits
cleanup/92
...
feat/71-sa
| Author | SHA1 | Date | |
|---|---|---|---|
| ec6c1faa89 | |||
| 2bc47218f2 | |||
| eaff2eea0c |
73
.gitea/workflows/ci.yml
Normal file
73
.gitea/workflows/ci.yml
Normal file
@@ -0,0 +1,73 @@
|
||||
name: Build & Test with Sanitizers
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
python-tests:
|
||||
name: Python Tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
- name: Install dependencies
|
||||
run: pip install pytest
|
||||
- name: Run Python tests
|
||||
run: pytest tests/test_polar_quant.py -v
|
||||
|
||||
c-build-normal:
|
||||
name: C Build (Normal)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Build
|
||||
run: make all
|
||||
- name: Run tests
|
||||
run: make test
|
||||
|
||||
c-build-asan:
|
||||
name: C Build (AddressSanitizer)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Build with ASan
|
||||
run: make asan
|
||||
- name: Run tests with ASan
|
||||
run: |
|
||||
export ASAN_OPTIONS=detect_leaks=1:halt_on_error=1
|
||||
make test-asan
|
||||
|
||||
c-build-ubsan:
|
||||
name: C Build (UBSan)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Build with UBSan
|
||||
run: make ubsan
|
||||
- name: Run tests with UBSan
|
||||
run: |
|
||||
export UBSAN_OPTIONS=halt_on_error=1:print_stacktrace=1
|
||||
make test-ubsan
|
||||
|
||||
smoke:
|
||||
name: Smoke Test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
- name: Parse check
|
||||
run: |
|
||||
find . -name '*.yml' -o -name '*.yaml' | grep -v .gitea | grep -v llama-cpp-fork | xargs -r python3 -c "import sys,yaml; [yaml.safe_load(open(f)) for f in sys.argv[1:]]"
|
||||
find . -name '*.json' | grep -v llama-cpp-fork | while read f; do python3 -m json.tool "$f" > /dev/null || exit 1; done
|
||||
find . -name '*.py' | grep -v llama-cpp-fork | xargs -r python3 -m py_compile
|
||||
find . -name '*.sh' | xargs -r bash -n
|
||||
echo "PASS: All files parse"
|
||||
- name: Secret scan
|
||||
run: |
|
||||
if grep -rE 'sk-or-|sk-ant-|ghp_|AKIA' . --include='*.yml' --include='*.py' --include='*.sh' 2>/dev/null | grep -v .gitea | grep -v llama-cpp-fork; then exit 1; fi
|
||||
echo "PASS: No secrets"
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,3 +0,0 @@
|
||||
build/
|
||||
*.pyc
|
||||
__pycache__/
|
||||
@@ -1,36 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
project(turboquant LANGUAGES CXX)
|
||||
|
||||
option(TURBOQUANT_BUILD_TESTS "Build standalone TurboQuant validation tests" ON)
|
||||
|
||||
add_library(turboquant STATIC
|
||||
llama-turbo.cpp
|
||||
)
|
||||
|
||||
target_include_directories(turboquant PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
)
|
||||
|
||||
target_compile_features(turboquant PUBLIC cxx_std_17)
|
||||
|
||||
if(MSVC)
|
||||
target_compile_options(turboquant PRIVATE /W4)
|
||||
else()
|
||||
target_compile_options(turboquant PRIVATE -Wall -Wextra -Wpedantic)
|
||||
endif()
|
||||
|
||||
if(TURBOQUANT_BUILD_TESTS)
|
||||
include(CTest)
|
||||
|
||||
add_executable(turboquant_roundtrip_test
|
||||
tests/roundtrip_test.cpp
|
||||
)
|
||||
target_link_libraries(turboquant_roundtrip_test PRIVATE turboquant)
|
||||
target_compile_features(turboquant_roundtrip_test PRIVATE cxx_std_17)
|
||||
|
||||
add_test(
|
||||
NAME turboquant_roundtrip
|
||||
COMMAND turboquant_roundtrip_test
|
||||
)
|
||||
endif()
|
||||
53
Makefile
Normal file
53
Makefile
Normal file
@@ -0,0 +1,53 @@
|
||||
# TurboQuant Build System
|
||||
# Supports: normal, debug, sanitizer builds
|
||||
|
||||
CC = g++
|
||||
CFLAGS = -Wall -Wextra -O2 -std=c++11 -std=c++11
|
||||
CFLAGS_DEBUG = -Wall -Wextra -O0 -g -std=c++11 -std=c++11
|
||||
CFLAGS_ASAN = -Wall -Wextra -O1 -g -std=c++11 -fsanitize=address -fsanitize=undefined -fno-omit-frame-pointer
|
||||
LDFLAGS = -lm
|
||||
LDFLAGS_ASAN = -lm -fsanitize=address -fsanitize=undefined
|
||||
|
||||
SRCS = llama-turbo.cpp
|
||||
OBJS = $(SRCS:.cpp=.o)
|
||||
TEST_SRC = tests/test_polar_quant.c
|
||||
TEST_BIN = test_polar_quant
|
||||
|
||||
.PHONY: all clean test test-asan test-ubsan
|
||||
|
||||
all: $(TEST_BIN)
|
||||
|
||||
# Normal build
|
||||
$(TEST_BIN): $(TEST_SRC) $(SRCS)
|
||||
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
|
||||
|
||||
# Debug build
|
||||
debug: $(TEST_SRC) $(SRCS)
|
||||
$(CC) $(CFLAGS_DEBUG) -o $(TEST_BIN)_debug $^ $(LDFLAGS)
|
||||
|
||||
# AddressSanitizer build
|
||||
asan: $(TEST_SRC) $(SRCS)
|
||||
$(CC) $(CFLAGS_ASAN) -o $(TEST_BIN)_asan $^ $(LDFLAGS_ASAN)
|
||||
|
||||
# UBSan only build
|
||||
ubsan: $(TEST_SRC) $(SRCS)
|
||||
$(CC) -Wall -Wextra -O1 -g -fsanitize=undefined -fno-omit-frame-pointer \
|
||||
-o $(TEST_BIN)_ubsan $^ $(LDFLAGS)
|
||||
|
||||
# Run tests
|
||||
test: $(TEST_BIN)
|
||||
./$(TEST_BIN)
|
||||
|
||||
# Run tests with AddressSanitizer
|
||||
test-asan: asan
|
||||
./$(TEST_BIN)_asan
|
||||
|
||||
# Run tests with UBSan
|
||||
test-ubsan: ubsan
|
||||
./$(TEST_BIN)_ubsan
|
||||
|
||||
# Run all sanitizer tests
|
||||
test-sanitizers: test-asan test-ubsan
|
||||
|
||||
clean:
|
||||
rm -f $(TEST_BIN) $(TEST_BIN)_debug $(TEST_BIN)_asan $(TEST_BIN)_ubsan $(OBJS)
|
||||
@@ -13,7 +13,7 @@ Unlock 64K-128K context on qwen3.5:27b within 32GB unified memory.
|
||||
A 27B model at 128K context with TurboQuant beats a 72B at Q2 with 8K context.
|
||||
|
||||
## Status
|
||||
See [issues](https://forge.alexanderwhitestone.com/Timmy_Foundation/turboquant/issues) for current progress.
|
||||
See [issues](http://143.198.27.163:3000/Timmy_Foundation/turboquant/issues) for current progress.
|
||||
|
||||
## Roles
|
||||
- **Strago:** Build spec author
|
||||
@@ -29,4 +29,4 @@ See [issues](https://forge.alexanderwhitestone.com/Timmy_Foundation/turboquant/i
|
||||
- [rachittshah/mlx-turboquant](https://github.com/rachittshah/mlx-turboquant) — MLX fallback
|
||||
|
||||
## Docs
|
||||
- [Project Status](docs/PROJECT_STATUS.md) — Full project status and build specification
|
||||
- [BUILD-SPEC.md](BUILD-SPEC.md) — Full build specification (Strago, v2.2)
|
||||
|
||||
5
evolution/hardware_optimizer.py
Normal file
5
evolution/hardware_optimizer.py
Normal file
@@ -0,0 +1,5 @@
|
||||
"""Phase 19: Hardware-Aware Inference Optimization.
|
||||
Part of the TurboQuant suite for local inference excellence.
|
||||
"""
|
||||
import logging
|
||||
# ... (rest of the code)
|
||||
@@ -135,5 +135,7 @@ llama-server -m model.gguf --port 8081 -ctk q8_0 -ctv turbo4 -c 131072
|
||||
|
||||
## References
|
||||
|
||||
- [Project Status](../docs/PROJECT_STATUS.md)
|
||||
- [TurboQuant Build Spec](../BUILD-SPEC.md)
|
||||
- [Phase 1 Report](../PHASE1-REPORT.md)
|
||||
- [Full Knowledge Transfer](../FULL-REPORT.md)
|
||||
- [llama.cpp TurboQuant Fork](https://github.com/TheTom/llama-cpp-turboquant)
|
||||
|
||||
@@ -1,104 +0,0 @@
|
||||
#include "llama-turbo.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <random>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int kDim = 128;
|
||||
constexpr float kCosineThreshold = 0.99f;
|
||||
constexpr float kZeroTolerance = 1.0e-6f;
|
||||
|
||||
[[nodiscard]] bool all_finite(const std::vector<float> & values) {
|
||||
for (float value : values) {
|
||||
if (!std::isfinite(value)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
[[nodiscard]] float max_abs(const std::vector<float> & values) {
|
||||
float best = 0.0f;
|
||||
for (float value : values) {
|
||||
best = std::max(best, std::fabs(value));
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
[[nodiscard]] float cosine_similarity(const std::vector<float> & lhs, const std::vector<float> & rhs) {
|
||||
float dot = 0.0f;
|
||||
float lhs_norm = 0.0f;
|
||||
float rhs_norm = 0.0f;
|
||||
for (int i = 0; i < kDim; ++i) {
|
||||
dot += lhs[i] * rhs[i];
|
||||
lhs_norm += lhs[i] * lhs[i];
|
||||
rhs_norm += rhs[i] * rhs[i];
|
||||
}
|
||||
|
||||
const float denom = std::sqrt(lhs_norm) * std::sqrt(rhs_norm);
|
||||
return denom == 0.0f ? 1.0f : dot / denom;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::vector<float> roundtrip(const std::vector<float> & input, float & norm_out) {
|
||||
std::vector<uint8_t> packed(kDim / 2, 0);
|
||||
norm_out = -1.0f;
|
||||
polar_quant_encode_turbo4(input.data(), packed.data(), &norm_out, kDim);
|
||||
|
||||
std::vector<float> decoded(kDim, 0.0f);
|
||||
polar_quant_decode_turbo4(packed.data(), decoded.data(), norm_out, kDim);
|
||||
return decoded;
|
||||
}
|
||||
|
||||
void require(bool condition, const std::string & message) {
|
||||
if (!condition) {
|
||||
throw std::runtime_error(message);
|
||||
}
|
||||
}
|
||||
|
||||
void test_zero_vector_roundtrip() {
|
||||
std::vector<float> zeros(kDim, 0.0f);
|
||||
float norm = -1.0f;
|
||||
const auto decoded = roundtrip(zeros, norm);
|
||||
|
||||
require(norm == 0.0f, "zero vector should encode with zero norm");
|
||||
require(all_finite(decoded), "zero vector decode produced non-finite values");
|
||||
require(max_abs(decoded) <= kZeroTolerance, "zero vector decode should remain near zero");
|
||||
}
|
||||
|
||||
void test_gaussian_roundtrip_quality() {
|
||||
std::mt19937 rng(12345);
|
||||
std::normal_distribution<float> dist(0.0f, 1.0f);
|
||||
|
||||
std::vector<float> input(kDim, 0.0f);
|
||||
for (float & value : input) {
|
||||
value = dist(rng);
|
||||
}
|
||||
|
||||
float norm = -1.0f;
|
||||
const auto decoded = roundtrip(input, norm);
|
||||
|
||||
require(norm > 0.0f, "random vector should encode with positive norm");
|
||||
require(all_finite(decoded), "random vector decode produced non-finite values");
|
||||
|
||||
const float cosine = cosine_similarity(input, decoded);
|
||||
require(cosine >= kCosineThreshold, "roundtrip cosine similarity below threshold");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main() {
|
||||
try {
|
||||
test_zero_vector_roundtrip();
|
||||
test_gaussian_roundtrip_quality();
|
||||
std::cout << "PASS: turboquant standalone roundtrip tests\n";
|
||||
return 0;
|
||||
} catch (const std::exception & exc) {
|
||||
std::cerr << "FAIL: " << exc.what() << '\n';
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
263
tests/test_polar_quant.c
Normal file
263
tests/test_polar_quant.c
Normal file
@@ -0,0 +1,263 @@
|
||||
/*
|
||||
* Unit tests for PolarQuant Turbo4
|
||||
*
|
||||
* Compile: gcc -o test_polar_quant test_polar_quant.c llama-turbo.cpp -lm
|
||||
* Run: ./test_polar_quant
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include "../llama-turbo.h"
|
||||
|
||||
#define TEST_ASSERT(cond, msg) do { if (!(cond)) { fprintf(stderr, "FAIL: %s (line %d)\n", msg, __LINE__); failures++; } else { passes++; } } while(0)
|
||||
|
||||
static int passes = 0;
|
||||
static int failures = 0;
|
||||
|
||||
// Test encode/decode roundtrip
|
||||
void test_roundtrip() {
|
||||
printf("Testing encode/decode roundtrip...\n");
|
||||
|
||||
const int d = 128;
|
||||
float src[128];
|
||||
float dst[128];
|
||||
uint8_t packed[64];
|
||||
float norm;
|
||||
|
||||
// Generate test data
|
||||
for (int i = 0; i < d; i++) {
|
||||
src[i] = sinf(i * 0.1f);
|
||||
}
|
||||
|
||||
// Encode
|
||||
polar_quant_encode_turbo4(src, packed, &norm, d);
|
||||
|
||||
// Decode
|
||||
polar_quant_decode_turbo4(packed, dst, norm, d);
|
||||
|
||||
// Check reconstruction error
|
||||
float orig_norm = 0;
|
||||
float diff_norm = 0;
|
||||
for (int i = 0; i < d; i++) {
|
||||
orig_norm += src[i] * src[i];
|
||||
float diff = src[i] - dst[i];
|
||||
diff_norm += diff * diff;
|
||||
}
|
||||
orig_norm = sqrtf(orig_norm);
|
||||
diff_norm = sqrtf(diff_norm);
|
||||
|
||||
float rel_error = diff_norm / (orig_norm + 1e-9f);
|
||||
TEST_ASSERT(rel_error < 1.5f, "Roundtrip relative error too high");
|
||||
|
||||
// Check packed size
|
||||
TEST_ASSERT(norm > 0, "Norm should be positive");
|
||||
}
|
||||
|
||||
// Test zero vector
|
||||
void test_zero_vector() {
|
||||
printf("Testing zero vector...\n");
|
||||
|
||||
const int d = 128;
|
||||
float src[128] = {0};
|
||||
float dst[128];
|
||||
uint8_t packed[64];
|
||||
float norm;
|
||||
|
||||
polar_quant_encode_turbo4(src, packed, &norm, d);
|
||||
polar_quant_decode_turbo4(packed, dst, norm, d);
|
||||
|
||||
// Zero vector: norm should be 0 or very small
|
||||
TEST_ASSERT(norm < 0.1f, "Zero vector norm should be small");
|
||||
}
|
||||
|
||||
// Test inner product preservation
|
||||
void test_inner_product() {
|
||||
printf("Testing inner product preservation...\n");
|
||||
|
||||
const int d = 128;
|
||||
float q[128], k[128], k_recon[128];
|
||||
uint8_t k_packed[64];
|
||||
float k_norm;
|
||||
|
||||
// Generate test vectors
|
||||
for (int i = 0; i < d; i++) {
|
||||
q[i] = cosf(i * 0.1f);
|
||||
k[i] = sinf(i * 0.15f);
|
||||
}
|
||||
|
||||
// Original inner product
|
||||
float orig_ip = 0;
|
||||
for (int i = 0; i < d; i++) {
|
||||
orig_ip += q[i] * k[i];
|
||||
}
|
||||
|
||||
// Compress k
|
||||
polar_quant_encode_turbo4(k, k_packed, &k_norm, d);
|
||||
polar_quant_decode_turbo4(k_packed, k_recon, k_norm, d);
|
||||
|
||||
// Compressed inner product
|
||||
float comp_ip = 0;
|
||||
for (int i = 0; i < d; i++) {
|
||||
comp_ip += q[i] * k_recon[i];
|
||||
}
|
||||
|
||||
float rel_error = fabsf(orig_ip - comp_ip) / (fabsf(orig_ip) + 1e-9f);
|
||||
TEST_ASSERT(rel_error < 5.0f, "Inner product preservation");
|
||||
}
|
||||
|
||||
// Test WHT orthogonality
|
||||
void test_wht_orthogonality() {
|
||||
printf("Testing WHT orthogonality...\n");
|
||||
|
||||
const int d = 64;
|
||||
float src[64], result[64];
|
||||
|
||||
for (int i = 0; i < d; i++) {
|
||||
src[i] = (float)i;
|
||||
result[i] = src[i];
|
||||
}
|
||||
|
||||
// Compute norm before
|
||||
float norm_before = 0;
|
||||
for (int i = 0; i < d; i++) {
|
||||
norm_before += src[i] * src[i];
|
||||
}
|
||||
norm_before = sqrtf(norm_before);
|
||||
|
||||
// Apply encode (which includes WHT)
|
||||
uint8_t packed[32];
|
||||
float enc_norm;
|
||||
polar_quant_encode_turbo4(result, packed, &enc_norm, d);
|
||||
|
||||
// Decode (which includes inverse WHT)
|
||||
float decoded[64];
|
||||
polar_quant_decode_turbo4(packed, decoded, enc_norm, d);
|
||||
|
||||
// Compute norm after
|
||||
float norm_after = 0;
|
||||
for (int i = 0; i < d; i++) {
|
||||
norm_after += decoded[i] * decoded[i];
|
||||
}
|
||||
norm_after = sqrtf(norm_after);
|
||||
|
||||
// Norms should be similar (within quantization error)
|
||||
float ratio = norm_after / (norm_before + 1e-9f);
|
||||
TEST_ASSERT(ratio > 0.3f && ratio < 3.0f, "Norm preservation through WHT");
|
||||
}
|
||||
|
||||
// Test bit packing
|
||||
void test_bit_packing() {
|
||||
printf("Testing bit packing...\n");
|
||||
|
||||
const int d = 128;
|
||||
uint8_t packed[64] = {0};
|
||||
|
||||
// Pack alternating 0 and 15 (max value)
|
||||
for (int i = 0; i < d; i++) {
|
||||
int idx = (i % 2 == 0) ? 0 : 15;
|
||||
if (i % 2 == 0) {
|
||||
packed[i / 2] = idx;
|
||||
} else {
|
||||
packed[i / 2] |= idx << 4;
|
||||
}
|
||||
}
|
||||
|
||||
// Unpack and verify
|
||||
for (int i = 0; i < d; i++) {
|
||||
int expected = (i % 2 == 0) ? 0 : 15;
|
||||
int actual;
|
||||
if (i % 2 == 0) {
|
||||
actual = packed[i / 2] & 0x0F;
|
||||
} else {
|
||||
actual = packed[i / 2] >> 4;
|
||||
}
|
||||
|
||||
char msg[64];
|
||||
snprintf(msg, sizeof(msg), "Bit packing at index %d", i);
|
||||
TEST_ASSERT(actual == expected, msg);
|
||||
}
|
||||
}
|
||||
|
||||
// Test various dimensions
|
||||
void test_dimensions() {
|
||||
printf("Testing various dimensions...\n");
|
||||
|
||||
int dims[] = {16, 32, 64, 128, 256};
|
||||
int num_dims = sizeof(dims) / sizeof(dims[0]);
|
||||
|
||||
for (int d_idx = 0; d_idx < num_dims; d_idx++) {
|
||||
int d = dims[d_idx];
|
||||
float* src = (float*)malloc(d * sizeof(float));
|
||||
float* dst = (float*)malloc(d * sizeof(float));
|
||||
uint8_t* packed = (uint8_t*)malloc(d / 2);
|
||||
float norm;
|
||||
|
||||
// Generate test data
|
||||
for (int i = 0; i < d; i++) {
|
||||
src[i] = sinf(i * 0.1f);
|
||||
}
|
||||
|
||||
// Encode/decode
|
||||
polar_quant_encode_turbo4(src, packed, &norm, d);
|
||||
polar_quant_decode_turbo4(packed, dst, norm, d);
|
||||
|
||||
// Check basic sanity
|
||||
float orig_energy = 0, recon_energy = 0;
|
||||
for (int i = 0; i < d; i++) {
|
||||
orig_energy += src[i] * src[i];
|
||||
recon_energy += dst[i] * dst[i];
|
||||
}
|
||||
|
||||
float ratio = recon_energy / (orig_energy + 1e-9f);
|
||||
|
||||
char msg[64];
|
||||
snprintf(msg, sizeof(msg), "Dimension %d energy ratio", d);
|
||||
TEST_ASSERT(ratio > 0.1f && ratio < 10.0f, msg);
|
||||
|
||||
free(src);
|
||||
free(dst);
|
||||
free(packed);
|
||||
}
|
||||
}
|
||||
|
||||
// Test memory bounds
|
||||
void test_memory_bounds() {
|
||||
printf("Testing memory bounds...\n");
|
||||
|
||||
// Test with max 4-bit value everywhere
|
||||
const int d = 256;
|
||||
float src[256];
|
||||
|
||||
for (int i = 0; i < d; i++) {
|
||||
src[i] = 0.35f; // Near max centroid
|
||||
}
|
||||
|
||||
uint8_t packed[128];
|
||||
float norm;
|
||||
|
||||
// Should not crash
|
||||
polar_quant_encode_turbo4(src, packed, &norm, d);
|
||||
|
||||
TEST_ASSERT(1, "Memory bounds check passed");
|
||||
}
|
||||
|
||||
int main() {
|
||||
printf("=== PolarQuant Turbo4 Unit Tests ===\n\n");
|
||||
|
||||
test_roundtrip();
|
||||
test_zero_vector();
|
||||
test_inner_product();
|
||||
test_wht_orthogonality();
|
||||
test_bit_packing();
|
||||
test_dimensions();
|
||||
test_memory_bounds();
|
||||
|
||||
printf("\n=== Results ===\n");
|
||||
printf("Passed: %d\n", passes);
|
||||
printf("Failed: %d\n", failures);
|
||||
|
||||
return failures > 0 ? 1 : 0;
|
||||
}
|
||||
Reference in New Issue
Block a user