Quantization Feedback: IQ4_NL vs Q4_K_P on budget hardware

#35
by Brassai - opened

Sharing some feedback on the quantization performance:

In my experience, IQ4_NL handles coding logic better than Q4_K_P. I had the model develop a fighting game twice; the version built by Q4_K_P couldn't run well, but the IQ4_NL version worked successfully.

Also, a quick note on efficiency: this model is incredible for budget rigs. I'm hitting around 30 t/s on my RTX 3050 8GB. Easily the best model of this size for my hardware.

Note I tested IQ4_XS and the Q4_K_P on budget hardware and the Q4_K_P will one shot a fractal generator where the IQ4_XS will need several tries before it gets it right.

Complex math, the Q4_K_P will absolutely trounce the IQ4 models that I have seen.

I forgot to mention, I am getting 43 to 45 t/s on the Q4_K_P with a 7800XT. For the IQ4_XS, I get 67 t/s. Traded some speed for some smarts.

FYI: /my_models/Qwen3.6-35B-A3B-Uncensored-HauhauCS-Aggressive-Q4_K_P.gguf · 1m 10s (43.02 tok/s) · Jun 28, 1:46 PM

Note I tested IQ4_XS and the Q4_K_P on budget hardware and the Q4_K_P will one shot a fractal generator where the IQ4_XS will need several tries before it gets it right.

Complex math, the Q4_K_P will absolutely trounce the IQ4 models that I have seen.

Great test. Have you had a chance to try IQ4_NL? Wondering if that one fares any better.

Well, this is a MoE model. To use IQ4 or below on it seriously damages its experts. I would stick to a full on Q4 if you can spare it. IQ4 will compress the FFN's to Q3 for example to save space. This is not as damaging on a dense model, but for an MoE, this is where your 256 experts live.

With that said, the IQ4 does perform decently anyway.

What are you looking for me to test? Playing with my Q4_K_P variant and it one shots things most of the time. Here is a Flappy Bird clone that was one shotted before work today:

import pygame
import sys
import os
import random

Constants

WIDTH, HEIGHT = 400, 600
FPS = 60
GRAVITY = 0.45
JUMP_STRENGTH = -7.5
PIPE_GAP = 150
PIPE_WIDTH = 60
GROUND_HEIGHT = 50
HIGHSCORE_FILE = "flippyblock_hs.txt"

class Bird:
def init(self, x, y):
self.rect = pygame.Rect(x, y, 30, 30)
self.velocity = 0
self.rotation = 0
self._img = self._create_image()

def _create_image(self):
    img = pygame.Surface((30, 30), pygame.SRCALPHA)
    pygame.draw.rect(img, (255, 215, 0), (0, 0, 30, 30), border_radius=6)
    pygame.draw.circle(img, (0, 0, 0), (22, 10), 4)
    pygame.draw.circle(img, (255, 255, 255), (23, 9), 1)
    pygame.draw.polygon(img, (255, 90, 0), [(25, 15), (36, 18), (25, 21)])
    return img

def jump(self):
    self.velocity = JUMP_STRENGTH

def update(self):
    self.velocity += GRAVITY
    self.rect.y += self.velocity
    self.rect.y = max(0, min(self.rect.y, HEIGHT - GROUND_HEIGHT - self.rect.height))
    self.rect.y = int(self.rect.y)  # Prevent subpixel drift
    self.rotation = min(max(self.velocity * 3, -25), 90)

def draw(self, surface):
    rotated = pygame.transform.rotate(self._img, -self.rotation)
    rect = rotated.get_rect(center=self.rect.center)
    surface.blit(rotated, rect.topleft)

class FlippyBlockExtreme:
def init(self):
pygame.init()
pygame.display.set_caption("FlippyBlock Extreme")
self.screen = pygame.display.set_mode((WIDTH, HEIGHT))
self.clock = pygame.time.Clock()
self.font_large = pygame.font.Font(None, 48)
self.font_med = pygame.font.Font(None, 32)
self.font_small = pygame.font.Font(None, 24)

    self.state = "START"
    self.bird = Bird(WIDTH // 4, HEIGHT // 2)
    self.pipes = []
    self.pipe_speed = 3.0
    self.pipe_spawn_timer = 0
    self.score = 0
    self.high_score = self._load_highscore()
    self.spawn_interval = 90  # frames

    # Cache static assets
    self.bg_surface = self._create_background()
    self.ground_surface = self._create_ground()

def _create_background(self):
    surf = pygame.Surface((WIDTH, HEIGHT))
    for y in range(HEIGHT):
        ratio = y / HEIGHT
        color = (int(135 * ratio), int(206 * ratio), int(235 * ratio))
        pygame.draw.line(surf, color, (0, y), (WIDTH, y))
    return surf

def _create_ground(self):
    surf = pygame.Surface((WIDTH, GROUND_HEIGHT))
    pygame.draw.rect(surf, (139, 119, 101), surf.get_rect())
    pygame.draw.rect(surf, (34, 139, 34), (0, 0, WIDTH, 10))
    return surf

def _load_highscore(self):
    try:
        with open(HIGHSCORE_FILE, "r") as f:
            return int(f.read().strip())
    except (IOError, ValueError):
        return 0

def _save_highscore(self, score):
    with open(HIGHSCORE_FILE, "w") as f:
        f.write(str(score))

def reset(self):
    self.state = "START"
    self.bird = Bird(WIDTH // 4, HEIGHT // 2)
    self.pipes = []
    self.pipe_speed = 3.0
    self.pipe_spawn_timer = 0
    self.score = 0
    self.spawn_interval = 90

def run(self):
    while True:
        dt = self.clock.tick(FPS) / 1000.0
        self._handle_events()
        self._update(dt)
        self._draw()
        pygame.display.flip()

def _handle_events(self):
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()
        if event.type in (pygame.KEYDOWN, pygame.MOUSEBUTTONDOWN, pygame.FINGERDOWN):
            if self.state == "START":
                self.state = "PLAYING"
                self.bird.jump()
            elif self.state == "PLAYING":
                self.bird.jump()
            elif self.state == "GAME_OVER":
                self.reset()

def _update(self, dt):
    if self.state != "PLAYING":
        return

    self.bird.update()

    # Dynamic spawn interval for difficulty
    self.spawn_interval = max(60, 90 - self.score * 0.5)

    self.pipe_spawn_timer += 1
    if self.pipe_spawn_timer >= self.spawn_interval:
        self.pipe_spawn_timer = 0
        gap_y = random.randint(PIPE_GAP // 2, HEIGHT - GROUND_HEIGHT - PIPE_GAP - PIPE_GAP // 2)
        self.pipes.append({
            "top": pygame.Rect(WIDTH, 0, PIPE_WIDTH, gap_y - PIPE_GAP // 2),
            "bottom": pygame.Rect(WIDTH, gap_y + PIPE_GAP // 2, PIPE_WIDTH, HEIGHT - GROUND_HEIGHT - gap_y - PIPE_GAP // 2)
        })

    # Move pipes & score
    for pipe in self.pipes:
        pipe["top"].x -= self.pipe_speed
        pipe["bottom"].x -= self.pipe_speed
        if not pipe.get("scored") and pipe["top"].right < self.bird.rect.left:
            pipe["scored"] = True
            self.score += 1
            self.pipe_speed = min(self.pipe_speed + 0.05, 6.0)

    # Remove off-screen
    self.pipes = [p for p in self.pipes if p["top"].right > 0]

    # Collision detection
    collision_rect = self.bird.rect.inflate(-4, -4)
    if collision_rect.bottom >= HEIGHT - GROUND_HEIGHT or collision_rect.top <= 0:
        self.state = "GAME_OVER"
    else:
        for pipe in self.pipes:
            if collision_rect.colliderect(pipe["top"]) or collision_rect.colliderect(pipe["bottom"]):
                self.state = "GAME_OVER"
                break

    # High score persistence
    if self.score > self.high_score:
        self.high_score = self.score
        self._save_highscore(self.high_score)

def _draw(self):
    self.screen.blit(self.bg_surface, (0, 0))
    self.screen.blit(self.ground_surface, (0, HEIGHT - GROUND_HEIGHT))

    # Draw pipes
    for pipe in self.pipes:
        color = (50, 205, 50)
        cap_color = (34, 139, 34)
        highlight = (100, 255, 100)
        for rect in (pipe["top"], pipe["bottom"]):
            pygame.draw.rect(self.screen, color, rect)
            cap_y = rect.y + rect.height - 10 if rect == pipe["top"] else rect.y
            pygame.draw.rect(self.screen, cap_color, (rect.x - 4, cap_y, rect.width + 8, 10))
            pygame.draw.rect(self.screen, highlight, (rect.x + 5, rect.y, 5, rect.height))

    self.bird.draw(self.screen)
    self._draw_ui()

def _draw_ui(self):
    if self.state == "START":
        self._draw_text("FLIPPYBLOCK EXTREME", (255, 255, 255), self.font_large, WIDTH // 2, HEIGHT // 3)
        self._draw_text("Press SPACE or Click to Start", (200, 200, 200), self.font_med, WIDTH // 2, HEIGHT // 2)
    elif self.state == "PLAYING":
        self._draw_text(str(self.score), (255, 255, 255), self.font_large, WIDTH // 2, 50)
    elif self.state == "GAME_OVER":
        self._draw_text("GAME OVER", (255, 69, 0), self.font_large, WIDTH // 2, HEIGHT // 3)
        self._draw_text(f"Score: {self.score}", (255, 255, 255), self.font_med, WIDTH // 2, HEIGHT // 2)
        self._draw_text(f"Best: {self.high_score}", (200, 200, 200), self.font_small, WIDTH // 2, HEIGHT // 2 + 40)
        self._draw_text("Press SPACE or Click to Restart", (200, 200, 200), self.font_small, WIDTH // 2, HEIGHT // 2 + 80)

def _draw_text(self, text, color, font, x, y):
    surf = font.render(text, True, color)
    rect = surf.get_rect(center=(x, y))
    self.screen.blit(surf, rect)

if name == "main":
game = FlippyBlockExtreme()
game.run()

This comment has been hidden (marked as Resolved)

Retesting

OK, retested. Model was about 50t/s. It took 2 iterations of the code to get it right, but it got it right on try two.

There is a definitely quality difference in both graphics and how the bird flies. Also it doesn't look like this one saves high scores across plays. As soon as you close the app, high score is lost.

Here is that code:

import pygame
import sys
import random

Constants

W, H = 400, 600
GRAVITY = 500
JUMP = -250
PIPE_SPEED = 200
PIPE_GAP = 150
PIPE_WIDTH = 50
BIRD_SIZE = 20
GROUND_H = 80

pygame.init()
screen = pygame.display.set_mode((W, H))
clock = pygame.time.Clock()
font = pygame.font.SysFont("Arial", 30, bold=True)
big_font = pygame.font.SysFont("Arial", 50, bold=True)

class Bird:
def init(self):
self.x = 80
self.y = H // 2
self.vel = 0
self.rect = pygame.Rect(self.x, self.y, BIRD_SIZE, BIRD_SIZE)
self.rotation = 0

def update(self, dt):
    self.vel += GRAVITY * dt
    self.y += self.vel * dt
    self.rect.center = (self.x, self.y)
    
    # Rotation based on velocity
    self.rotation = min(max(self.vel * 0.1, -25), 90)
    
    # Floor collision
    if self.y > H - GROUND_H - BIRD_SIZE // 2:
        return True
    # Ceiling collision
    if self.y < 0:
        return True
    return False

def jump(self):
    self.vel = JUMP

def draw(self):
    pygame.draw.circle(screen, (244, 208, 63), (int(self.x), int(self.y)), BIRD_SIZE // 2)
    pygame.draw.circle(screen, (212, 172, 13), (int(self.x), int(self.y)), BIRD_SIZE // 2, 2)
    # Eye
    pygame.draw.circle(screen, (0, 0, 0), (int(self.x + 5), int(self.y - 5)), 3)

class Pipe:
def init(self, x):
self.x = x
self.top_h = random.randint(50, H - GROUND_H - PIPE_GAP - 50)
self.bottom_y = self.top_h + PIPE_GAP
self.width = PIPE_WIDTH
self.passed = False

def update(self, dt):
    self.x -= PIPE_SPEED * dt

def draw(self):
    # Top Pipe
    pygame.draw.rect(screen, (115, 191, 46), (self.x, 0, self.width, self.top_h))
    pygame.draw.rect(screen, (85, 140, 34), (self.x, 0, self.width, self.top_h), 3)
    # Bottom Pipe
    pygame.draw.rect(screen, (115, 191, 46), (self.x, self.bottom_y, self.width, H - self.bottom_y - GROUND_H))
    pygame.draw.rect(screen, (85, 140, 34), (self.x, self.bottom_y, self.width, H - self.bottom_y - GROUND_H), 3)

def check_collision(self, bird):
    bird_rect = pygame.Rect(bird.x - BIRD_SIZE//2, bird.y - BIRD_SIZE//2, BIRD_SIZE, BIRD_SIZE)
    top_rect = pygame.Rect(self.x, 0, self.width, self.top_h)
    bot_rect = pygame.Rect(self.x, self.bottom_y, self.width, H - self.bottom_y - GROUND_H)
    
    if bird_rect.colliderect(top_rect) or bird_rect.colliderect(bot_rect):
        return True
    return False

def reset_game():
bird = Bird()
pipes = []
score = 0
pipe_timer = 0
return bird, pipes, score, pipe_timer

def main():
bird, pipes, score, pipe_timer = reset_game()
state = 'START' # START, PLAY, DEAD
last_time = pygame.time.get_ticks()

while True:
    dt = (pygame.time.get_ticks() - last_time) / 1000.0
    last_time = pygame.time.get_ticks()
    dt = min(dt, 0.1) # Cap delta to prevent physics explosions on lag

    screen.fill((112, 197, 206))

    # Input
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()
        if event.type == pygame.KEYDOWN or event.type == pygame.MOUSEBUTTONDOWN:
            if state == 'START':
                state = 'PLAY'
                bird, pipes, score, pipe_timer = reset_game()
                bird.jump()
            elif state == 'PLAY':
                bird.jump()
            elif state == 'DEAD':
                state = 'START'

    # Logic
    if state == 'PLAY':
        # Bird Physics
        if bird.update(dt):
            state = 'DEAD'

        # Pipe Spawning
        pipe_timer += dt
        if pipe_timer > 1.5:
            pipes.append(Pipe(W))
            pipe_timer = 0

        # Pipe Movement & Collision
        for p in pipes:
            p.update(dt)
            if p.check_collision(bird):
                state = 'DEAD'
            
            # Score
            if not p.passed and p.x < bird.x:
                score += 1
                p.passed = True

        # Cleanup
        pipes = [p for p in pipes if p.x > -50]

    # Drawing
    # Ground
    pygame.draw.rect(screen, (222, 216, 149), (0, H - GROUND_H, W, GROUND_H))
    pygame.draw.rect(screen, (84, 185, 72), (0, H - GROUND_H, W, 12))

    # Pipes
    for p in pipes:
        p.draw()

    # Bird
    bird.draw()

    # UI
    if state == 'PLAY':
        txt = font.render(str(score), True, (255, 255, 255))
        screen.blit(txt, (W // 2 - txt.get_width() // 2, 50))
    elif state == 'START':
        txt = big_font.render('FLAPPY CLONE', True, (255, 255, 255))
        screen.blit(txt, (W // 2 - txt.get_width() // 2, H // 2 - 100))
        txt2 = font.render('Click or Space to Start', True, (255, 255, 255))
        screen.blit(txt2, (W // 2 - txt2.get_width() // 2, H // 2))
    elif state == 'DEAD':
        txt = big_font.render('GAME OVER', True, (255, 255, 255))
        screen.blit(txt, (W // 2 - txt.get_width() // 2, H // 2 - 80))
        txt2 = font.render(f'Score: {score}', True, (255, 255, 255))
        screen.blit(txt2, (W // 2 - txt2.get_width() // 2, H // 2 - 20))

    pygame.display.flip()
    clock.tick(60)

if name == 'main':
main()

This comment has been hidden (marked as Resolved)

Sign up or log in to comment