I trained a bot in rlgym using 8 tick skip, this is my wrapper for running the bot in RLBot. Is there any way to improve performance of the bot, maybe there's a trick like running inference more than once. If you come up with an idea provide the full code so I can copy and paste , I separated the code using # file_name.py but I would prefer if you just separated the code properly. 
#agent.py
import os
import numpy as np

import torch

from discrete_policy import DiscreteFF
from your_act import LookupAction

# You can get the OBS size from the rlgym-ppo console print-outs when you start your bot
OBS_SIZE = 246

# If you haven't set these, they are [256, 256, 256] by default
POLICY_LAYER_SIZES = [512, 512, 512, 512]

class Agent:
	def __init__(self):
		self.action_parser = LookupAction()
		self.num_actions = len(self.action_parser._lookup_table)
		cur_dir = os.path.dirname(os.path.realpath(__file__))
		
		device = torch.device("cpu")
		self.policy = DiscreteFF(OBS_SIZE, self.num_actions, POLICY_LAYER_SIZES, device)
		self.policy.load_state_dict(torch.load(os.path.join(cur_dir, "PPO_POLICY.pt"), map_location=device))
		torch.set_num_threads(1)

	def act(self, state):
		with torch.no_grad():
			action_idx, probs = self.policy.get_action(state, True)
		
		action = np.array(self.action_parser.parse_actions([action_idx]))
		if len(action.shape) == 2:
			if action.shape[0] == 1:
				action = action[0]
		
		if len(action.shape) != 1:
			raise Exception("Invalid action:", action)
		
		return action

#bot.py
import numpy as np

from rlbot.agents.base_agent import BaseAgent, SimpleControllerState
from rlbot.utils.structures.game_data_struct import GameTickPacket

from util import common_values
from util.game_state import GameState
from agent import Agent
from your_obs import EmbeddedOBS

class RLGymPPOBot(BaseAgent):
	def __init__(self, name, team, index):
		super().__init__(name, team, index)
		self.obs_builder = EmbeddedOBS()
		self.agent = Agent()
		self.tick_skip = 8
		self.game_state: GameState = None
		self.controls = None
		self.action = None
		self.update_action = True
		self.ticks = 0
		self.prev_time = 0
		print('====================================')
		print('NdroidE Ready - Index:', index)
		print('Make sure your FPS is at 120, 240, or 360!')
		print('====================================')

	def is_hot_reload_enabled(self):
		return True

	def initialize_agent(self):
		# Initialize the rlgym GameState object now that the game is active and the info is available
		self.game_state = GameState(self.get_field_info())
		self.update_action = True
		self.ticks = self.tick_skip  # So we take an action the first tick
		self.prev_time = 0
		self.controls = SimpleControllerState()
		self.action = np.zeros(8)

	def get_output(self, packet: GameTickPacket) -> SimpleControllerState:
		cur_time = packet.game_info.seconds_elapsed
		delta = cur_time - self.prev_time
		self.prev_time = cur_time

		ticks_elapsed = round(delta * 120)
		self.ticks += ticks_elapsed
		self.game_state.decode(packet, ticks_elapsed)

		# We calculate the next action as soon as the prev action is sent
		# This gives you tick_skip ticks to do your forward pass
		if self.update_action:
			self.update_action = False

			player = self.game_state.players[self.index]
			
			obs = self.obs_builder.build_obs(player, self.game_state, self.action)
			self.action = self.agent.act(obs)

		if self.ticks >= self.tick_skip - 1:
			self.update_controls(self.action)

		if self.ticks >= self.tick_skip:
			self.ticks = 0
			self.update_action = True

		return self.controls

	def update_controls(self, action):
		self.controls.throttle = action[0]
		self.controls.steer = action[1]
		self.controls.pitch = action[2]
		self.controls.yaw = action[3]
		self.controls.roll = action[4]
		self.controls.jump = action[5] > 0
		self.controls.boost = action[6] > 0
		self.controls.handbrake = action[7] > 0

#your_obs.py
import numpy as np
import random
from typing import List, Tuple

class CommonValues:
    SIDE_WALL_X = 4096
    BACK_WALL_Y = 5120
    CEILING_Z = 2044
    CAR_MAX_SPEED = 2300
    CAR_MAX_ANG_VEL = 5.5
    BALL_MAX_SPEED = 6000
    BOOST_LOCATIONS_AMOUNT = 34

class Team:
    BLUE = 0
    ORANGE = 1

class EmbeddedOBS:
    def __init__(self,
                 teammate_dim: int = 68,
                 opponent_dim: int = 102,
                 ball_dim: int = 9,
                 player_dim: int = 25,
                 pos_coef: Tuple[float, float, float] = (1 / CommonValues.SIDE_WALL_X, 1 / CommonValues.BACK_WALL_Y, 1 / CommonValues.CEILING_Z),
                 vel_coef: float = 1 / CommonValues.CAR_MAX_SPEED,
                 ang_vel_coef: float = 1 / CommonValues.CAR_MAX_ANG_VEL,
                 rel_pos_coef: Tuple[float, float, float] = (1 / (2 * CommonValues.SIDE_WALL_X), 1 / (2 * CommonValues.BACK_WALL_Y), 1 / CommonValues.CEILING_Z),
                 rel_vel_coef: float = 1 / (2 * CommonValues.CAR_MAX_SPEED),
                 rel_ang_vel_coef: float = 1 / (2 * CommonValues.CAR_MAX_ANG_VEL),
                 ball_vel_coef: float = 1 / CommonValues.BALL_MAX_SPEED,
                 ball_ang_vel_coef: float = 1 / 6.0,
                 rel_ball_vel_coef: float = 1 / (CommonValues.BALL_MAX_SPEED + CommonValues.CAR_MAX_SPEED),
                 rel_ball_ang_vel_coef: float = 1 / (CommonValues.CAR_MAX_ANG_VEL + 6.0)):
        self.teammate_dim = teammate_dim
        self.opponent_dim = opponent_dim
        self.ball_dim = ball_dim
        self.player_dim = player_dim
        self.pos_coef = np.array(pos_coef)
        self.vel_coef = vel_coef
        self.ang_vel_coef = ang_vel_coef
        self.rel_pos_coef = np.array(rel_pos_coef)
        self.rel_vel_coef = rel_vel_coef
        self.rel_ang_vel_coef = rel_ang_vel_coef
        self.ball_vel_coef = ball_vel_coef
        self.ball_ang_vel_coef = ball_ang_vel_coef
        self.rel_ball_vel_coef = rel_ball_vel_coef
        self.rel_ball_ang_vel_coef = rel_ball_ang_vel_coef

    def build_obs(self, player: 'PlayerData', state: 'GameState', prev_action: List[float]) -> List[float]:
        result = []
        inv = player.team_num == Team.ORANGE

        # Process ball information
        result.extend(self.process_ball(state.inverted_ball if inv else state.ball))

        # Add boost pad information
        pads = state.inverted_boost_pads if inv else state.boost_pads
        result.extend(pads)

        # Add previous action
        result.extend(prev_action)

        # Process self information
        result.extend(self.process_local_player(player, state.inverted_ball if inv else state.ball, inv))

        # Process teammates
        result.extend(self.process_players(state.players, player.team_num, player.car_id, 
                                           state.inverted_ball if inv else state.ball, 
                                           player.inverted_car_data if inv else player.car_data, 
                                           inv, self.teammate_dim))

        # Process opponents
        result.extend(self.process_players(state.players, Team.ORANGE if player.team_num == Team.BLUE else Team.BLUE, 
                                           player.car_id, state.inverted_ball if inv else state.ball, 
                                           player.inverted_car_data if inv else player.car_data, 
                                           inv, self.opponent_dim))

        return result

    def process_ball(self, ball: 'PhysicsObject') -> List[float]:
        ball_obs = [
            *ball.position * self.pos_coef,
            *ball.linear_velocity * self.ball_vel_coef,
            *ball.angular_velocity * self.ball_ang_vel_coef
        ]
        return self.embed_info(ball_obs, self.ball_dim)

    def process_local_player(self, player: 'PlayerData', ball: 'PhysicsObject', inv: bool) -> List[float]:
        player_obs = self.add_local_player_to_obs(player, ball, inv)
        return self.embed_info(player_obs, self.player_dim)

    def process_players(self, players: List['PlayerData'], team: int, self_id: int, 
                        ball: 'PhysicsObject', local_player: 'PhysicsObject', inv: bool, embedding_size: int) -> List[float]:
        player_observations = []

        for other_player in players:
            if other_player.team_num == team and other_player.car_id != self_id:
                player_obs = self.add_player_to_obs(other_player, ball, local_player, inv)
                player_observations.append(player_obs)

        # Shuffle player observations
        random.shuffle(player_observations)

        return self.embed_info(player_observations, embedding_size)

    def add_local_player_to_obs(self, player: 'PlayerData', ball: 'PhysicsObject', inv: bool) -> List[float]:
        car_data = player.inverted_car_data if inv else player.car_data
        obs = []

        # Relative position to ball
        rel_pos_to_ball = (ball.position - car_data.position) * self.rel_pos_coef
        obs.extend(rel_pos_to_ball)

        # Relative velocity to ball
        rel_vel_to_ball = (ball.linear_velocity - car_data.linear_velocity) * self.rel_ball_vel_coef
        obs.extend(rel_vel_to_ball)

        # Player's position
        obs.extend(car_data.position * self.pos_coef)

        # Player's velocity and angular velocity
        obs.extend(car_data.linear_velocity * self.vel_coef)
        obs.extend(car_data.angular_velocity * self.ang_vel_coef)

        # Player's orientation
        obs.extend(car_data.forward())
        obs.extend(car_data.up())

        # Additional player state information
        obs.extend([
            player.boost_amount,
            float(player.on_ground),
            float(player.has_flip),
            float(player.is_demoed),
        ])

        return obs

    def add_player_to_obs(self, player: 'PlayerData', ball: 'PhysicsObject', local_player: 'PhysicsObject', inv: bool) -> List[float]:
        car_data = player.inverted_car_data if inv else player.car_data
        obs = []

        # Relative position to local player
        obs.extend((car_data.position - local_player.position) * self.rel_pos_coef)

        # Relative velocity to local player
        obs.extend((car_data.linear_velocity - local_player.linear_velocity) * self.rel_vel_coef)

        # Relative angular velocity to local player
        obs.extend((car_data.angular_velocity - local_player.angular_velocity) * self.rel_ang_vel_coef)

        # Relative position to ball
        rel_pos_to_ball = (ball.position - car_data.position) * self.rel_pos_coef
        obs.extend(rel_pos_to_ball)

        # Relative velocity to ball
        rel_vel_to_ball = (ball.linear_velocity - car_data.linear_velocity) * self.rel_ball_vel_coef
        obs.extend(rel_vel_to_ball)

        # Player's position
        obs.extend(car_data.position * self.pos_coef)

        # Player's velocity and angular velocity
        obs.extend(car_data.linear_velocity * self.vel_coef)
        obs.extend(car_data.angular_velocity * self.ang_vel_coef)

        # Player's orientation
        obs.extend(car_data.forward())
        obs.extend(car_data.up())

        # Additional player state information
        obs.extend([
            player.boost_amount,
            float(player.on_ground),
            float(player.has_flip),
            float(player.is_demoed),
        ])

        return obs

    def embed_info(self, info: List[float], embedding_size: int) -> List[float]:
        if not info:
            return [0.0] * embedding_size
        
        if isinstance(info[0], list):  # List of lists
            embedding = [0.0] * embedding_size
            for i in range(embedding_size):
                sum_values = sum(player_info[i % len(player_info)] for player_info in info)
                embedding[i] = sum_values / len(info)
        else:  # Single list
            embedding = [info[i % len(info)] for i in range(embedding_size)]
        
        return embedding